init
This commit is contained in:
commit
72a26edcff
22092 changed files with 2101903 additions and 0 deletions
77
lib/PhpSpreadsheet/Calculation/TextData/CaseConvert.php
Normal file
77
lib/PhpSpreadsheet/Calculation/TextData/CaseConvert.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class CaseConvert
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* LOWERCASE.
|
||||
*
|
||||
* Converts a string value to upper case.
|
||||
*
|
||||
* @param mixed $mixedCaseValue The string value to convert to lower case
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed as the argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function lower(mixed $mixedCaseValue): array|string
|
||||
{
|
||||
if (is_array($mixedCaseValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
|
||||
}
|
||||
|
||||
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
|
||||
|
||||
return StringHelper::strToLower($mixedCaseValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* UPPERCASE.
|
||||
*
|
||||
* Converts a string value to upper case.
|
||||
*
|
||||
* @param mixed $mixedCaseValue The string value to convert to upper case
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed as the argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function upper(mixed $mixedCaseValue): array|string
|
||||
{
|
||||
if (is_array($mixedCaseValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
|
||||
}
|
||||
|
||||
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
|
||||
|
||||
return StringHelper::strToUpper($mixedCaseValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* PROPERCASE.
|
||||
*
|
||||
* Converts a string value to proper or title case.
|
||||
*
|
||||
* @param mixed $mixedCaseValue The string value to convert to title case
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed as the argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function proper(mixed $mixedCaseValue): array|string
|
||||
{
|
||||
if (is_array($mixedCaseValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
|
||||
}
|
||||
|
||||
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
|
||||
|
||||
return StringHelper::strToTitle($mixedCaseValue);
|
||||
}
|
||||
}
|
||||
81
lib/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
Normal file
81
lib/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class CharacterConvert
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* CHAR.
|
||||
*
|
||||
* @param mixed $character Integer Value to convert to its character representation
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string The character string
|
||||
* If an array of values is passed as the argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function character(mixed $character): array|string
|
||||
{
|
||||
if (is_array($character)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $character);
|
||||
}
|
||||
|
||||
$character = Helpers::validateInt($character);
|
||||
$min = Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE ? 0 : 1;
|
||||
if ($character < $min || $character > 255) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
$result = iconv('UCS-4LE', 'UTF-8', pack('V', $character));
|
||||
|
||||
return ($result === false) ? '' : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* CODE.
|
||||
*
|
||||
* @param mixed $characters String character to convert to its ASCII value
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|int|string A string if arguments are invalid
|
||||
* If an array of values is passed as the argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function code(mixed $characters): array|string|int
|
||||
{
|
||||
if (is_array($characters)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $characters);
|
||||
}
|
||||
|
||||
$characters = Helpers::extractString($characters);
|
||||
if ($characters === '') {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
$character = $characters;
|
||||
if (mb_strlen($characters, 'UTF-8') > 1) {
|
||||
$character = mb_substr($characters, 0, 1, 'UTF-8');
|
||||
}
|
||||
|
||||
return self::unicodeToOrd($character);
|
||||
}
|
||||
|
||||
private static function unicodeToOrd(string $character): int
|
||||
{
|
||||
$retVal = 0;
|
||||
$iconv = iconv('UTF-8', 'UCS-4LE', $character);
|
||||
if ($iconv !== false) {
|
||||
$result = unpack('V', $iconv);
|
||||
if (is_array($result) && isset($result[1])) {
|
||||
$retVal = $result[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
}
|
||||
137
lib/PhpSpreadsheet/Calculation/TextData/Concatenate.php
Normal file
137
lib/PhpSpreadsheet/Calculation/TextData/Concatenate.php
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class Concatenate
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* CONCATENATE.
|
||||
*
|
||||
* @param array $args
|
||||
*/
|
||||
public static function CONCATENATE(...$args): string
|
||||
{
|
||||
$returnValue = '';
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
|
||||
foreach ($aArgs as $arg) {
|
||||
$value = Helpers::extractString($arg);
|
||||
if (ErrorValue::isError($value)) {
|
||||
$returnValue = $value;
|
||||
|
||||
break;
|
||||
}
|
||||
$returnValue .= Helpers::extractString($arg);
|
||||
if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) {
|
||||
$returnValue = ExcelError::CALC();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* TEXTJOIN.
|
||||
*
|
||||
* @param mixed $delimiter The delimter to use between the joined arguments
|
||||
* Or can be an array of values
|
||||
* @param mixed $ignoreEmpty true/false Flag indicating whether empty arguments should be skipped
|
||||
* Or can be an array of values
|
||||
* @param mixed $args The values to join
|
||||
*
|
||||
* @return array|string The joined string
|
||||
* If an array of values is passed for the $delimiter or $ignoreEmpty arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function TEXTJOIN(mixed $delimiter = '', mixed $ignoreEmpty = true, mixed ...$args): array|string
|
||||
{
|
||||
if (is_array($delimiter) || is_array($ignoreEmpty)) {
|
||||
return self::evaluateArrayArgumentsSubset(
|
||||
[self::class, __FUNCTION__],
|
||||
2,
|
||||
$delimiter,
|
||||
$ignoreEmpty,
|
||||
...$args
|
||||
);
|
||||
}
|
||||
|
||||
$delimiter ??= '';
|
||||
$ignoreEmpty ??= true;
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
$returnValue = self::evaluateTextJoinArray($ignoreEmpty, $aArgs);
|
||||
|
||||
$returnValue ??= implode($delimiter, $aArgs);
|
||||
if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) {
|
||||
$returnValue = ExcelError::CALC();
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
private static function evaluateTextJoinArray(bool $ignoreEmpty, array &$aArgs): ?string
|
||||
{
|
||||
foreach ($aArgs as $key => &$arg) {
|
||||
$value = Helpers::extractString($arg);
|
||||
if (ErrorValue::isError($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($ignoreEmpty === true && ((is_string($arg) && trim($arg) === '') || $arg === null)) {
|
||||
unset($aArgs[$key]);
|
||||
} elseif (is_bool($arg)) {
|
||||
$arg = Helpers::convertBooleanValue($arg);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* REPT.
|
||||
*
|
||||
* Returns the result of builtin function round after validating args.
|
||||
*
|
||||
* @param mixed $stringValue The value to repeat
|
||||
* Or can be an array of values
|
||||
* @param mixed $repeatCount The number of times the string value should be repeated
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string The repeated string
|
||||
* If an array of values is passed for the $stringValue or $repeatCount arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function builtinREPT(mixed $stringValue, mixed $repeatCount): array|string
|
||||
{
|
||||
if (is_array($stringValue) || is_array($repeatCount)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $stringValue, $repeatCount);
|
||||
}
|
||||
|
||||
$stringValue = Helpers::extractString($stringValue);
|
||||
|
||||
if (!is_numeric($repeatCount) || $repeatCount < 0) {
|
||||
$returnValue = ExcelError::VALUE();
|
||||
} elseif (ErrorValue::isError($stringValue)) {
|
||||
$returnValue = $stringValue;
|
||||
} else {
|
||||
$returnValue = str_repeat($stringValue, (int) $repeatCount);
|
||||
if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) {
|
||||
$returnValue = ExcelError::VALUE(); // note VALUE not CALC
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
}
|
||||
270
lib/PhpSpreadsheet/Calculation/TextData/Extract.php
Normal file
270
lib/PhpSpreadsheet/Calculation/TextData/Extract.php
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class Extract
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* LEFT.
|
||||
*
|
||||
* @param mixed $value String value from which to extract characters
|
||||
* Or can be an array of values
|
||||
* @param mixed $chars The number of characters to extract (as an integer)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string The joined string
|
||||
* If an array of values is passed for the $value or $chars arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function left(mixed $value, mixed $chars = 1): array|string
|
||||
{
|
||||
if (is_array($value) || is_array($chars)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = Helpers::extractString($value);
|
||||
$chars = Helpers::extractInt($chars, 0, 1);
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return mb_substr($value, 0, $chars, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* MID.
|
||||
*
|
||||
* @param mixed $value String value from which to extract characters
|
||||
* Or can be an array of values
|
||||
* @param mixed $start Integer offset of the first character that we want to extract
|
||||
* Or can be an array of values
|
||||
* @param mixed $chars The number of characters to extract (as an integer)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string The joined string
|
||||
* If an array of values is passed for the $value, $start or $chars arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function mid(mixed $value, mixed $start, mixed $chars): array|string
|
||||
{
|
||||
if (is_array($value) || is_array($start) || is_array($chars)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $start, $chars);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = Helpers::extractString($value);
|
||||
$start = Helpers::extractInt($start, 1);
|
||||
$chars = Helpers::extractInt($chars, 0);
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return mb_substr($value, --$start, $chars, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* RIGHT.
|
||||
*
|
||||
* @param mixed $value String value from which to extract characters
|
||||
* Or can be an array of values
|
||||
* @param mixed $chars The number of characters to extract (as an integer)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string The joined string
|
||||
* If an array of values is passed for the $value or $chars arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function right(mixed $value, mixed $chars = 1): array|string
|
||||
{
|
||||
if (is_array($value) || is_array($chars)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = Helpers::extractString($value);
|
||||
$chars = Helpers::extractInt($chars, 0, 1);
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* TEXTBEFORE.
|
||||
*
|
||||
* @param mixed $text the text that you're searching
|
||||
* Or can be an array of values
|
||||
* @param null|array|string $delimiter the text that marks the point before which you want to extract
|
||||
* Multiple delimiters can be passed as an array of string values
|
||||
* @param mixed $instance The instance of the delimiter after which you want to extract the text.
|
||||
* By default, this is the first instance (1).
|
||||
* A negative value means start searching from the end of the text string.
|
||||
* Or can be an array of values
|
||||
* @param mixed $matchMode Determines whether the match is case-sensitive or not.
|
||||
* 0 - Case-sensitive
|
||||
* 1 - Case-insensitive
|
||||
* Or can be an array of values
|
||||
* @param mixed $matchEnd Treats the end of text as a delimiter.
|
||||
* 0 - Don't match the delimiter against the end of the text.
|
||||
* 1 - Match the delimiter against the end of the text.
|
||||
* Or can be an array of values
|
||||
* @param mixed $ifNotFound value to return if no match is found
|
||||
* The default is a #N/A Error
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string the string extracted from text before the delimiter; or the $ifNotFound value
|
||||
* If an array of values is passed for any of the arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function before(mixed $text, $delimiter, mixed $instance = 1, mixed $matchMode = 0, mixed $matchEnd = 0, mixed $ifNotFound = '#N/A'): array|string
|
||||
{
|
||||
if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) {
|
||||
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
|
||||
}
|
||||
|
||||
$text = Helpers::extractString($text ?? '');
|
||||
$instance = (int) $instance;
|
||||
$matchMode = (int) $matchMode;
|
||||
$matchEnd = (int) $matchEnd;
|
||||
|
||||
$split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
|
||||
if (is_string($split)) {
|
||||
return $split;
|
||||
}
|
||||
if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') {
|
||||
return ($instance > 0) ? '' : $text;
|
||||
}
|
||||
|
||||
// Adjustment for a match as the first element of the split
|
||||
$flags = self::matchFlags($matchMode);
|
||||
$delimiter = self::buildDelimiter($delimiter);
|
||||
$adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]);
|
||||
$oddReverseAdjustment = count($split) % 2;
|
||||
|
||||
$split = ($instance < 0)
|
||||
? array_slice($split, 0, max(count($split) - (abs($instance) * 2 - 1) - $adjust - $oddReverseAdjustment, 0))
|
||||
: array_slice($split, 0, $instance * 2 - 1 - $adjust);
|
||||
|
||||
return implode('', $split);
|
||||
}
|
||||
|
||||
/**
|
||||
* TEXTAFTER.
|
||||
*
|
||||
* @param mixed $text the text that you're searching
|
||||
* @param null|array|string $delimiter the text that marks the point before which you want to extract
|
||||
* Multiple delimiters can be passed as an array of string values
|
||||
* @param mixed $instance The instance of the delimiter after which you want to extract the text.
|
||||
* By default, this is the first instance (1).
|
||||
* A negative value means start searching from the end of the text string.
|
||||
* Or can be an array of values
|
||||
* @param mixed $matchMode Determines whether the match is case-sensitive or not.
|
||||
* 0 - Case-sensitive
|
||||
* 1 - Case-insensitive
|
||||
* Or can be an array of values
|
||||
* @param mixed $matchEnd Treats the end of text as a delimiter.
|
||||
* 0 - Don't match the delimiter against the end of the text.
|
||||
* 1 - Match the delimiter against the end of the text.
|
||||
* Or can be an array of values
|
||||
* @param mixed $ifNotFound value to return if no match is found
|
||||
* The default is a #N/A Error
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string the string extracted from text before the delimiter; or the $ifNotFound value
|
||||
* If an array of values is passed for any of the arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function after(mixed $text, $delimiter, mixed $instance = 1, mixed $matchMode = 0, mixed $matchEnd = 0, mixed $ifNotFound = '#N/A'): array|string
|
||||
{
|
||||
if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) {
|
||||
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
|
||||
}
|
||||
|
||||
$text = Helpers::extractString($text ?? '');
|
||||
$instance = (int) $instance;
|
||||
$matchMode = (int) $matchMode;
|
||||
$matchEnd = (int) $matchEnd;
|
||||
|
||||
$split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
|
||||
if (is_string($split)) {
|
||||
return $split;
|
||||
}
|
||||
if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') {
|
||||
return ($instance < 0) ? '' : $text;
|
||||
}
|
||||
|
||||
// Adjustment for a match as the first element of the split
|
||||
$flags = self::matchFlags($matchMode);
|
||||
$delimiter = self::buildDelimiter($delimiter);
|
||||
$adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]);
|
||||
$oddReverseAdjustment = count($split) % 2;
|
||||
|
||||
$split = ($instance < 0)
|
||||
? array_slice($split, count($split) - ((int) abs($instance + 1) * 2) - $adjust - $oddReverseAdjustment)
|
||||
: array_slice($split, $instance * 2 - $adjust);
|
||||
|
||||
return implode('', $split);
|
||||
}
|
||||
|
||||
private static function validateTextBeforeAfter(string $text, null|array|string $delimiter, int $instance, int $matchMode, int $matchEnd, mixed $ifNotFound): array|string
|
||||
{
|
||||
$flags = self::matchFlags($matchMode);
|
||||
$delimiter = self::buildDelimiter($delimiter);
|
||||
|
||||
if (preg_match('/' . $delimiter . "/{$flags}", $text) === 0 && $matchEnd === 0) {
|
||||
return $ifNotFound;
|
||||
}
|
||||
|
||||
$split = preg_split('/' . $delimiter . "/{$flags}", $text, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
|
||||
if ($split === false) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
|
||||
if ($instance === 0 || abs($instance) > StringHelper::countCharacters($text)) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
if ($matchEnd === 0 && (abs($instance) > floor(count($split) / 2))) {
|
||||
return ExcelError::NA();
|
||||
} elseif ($matchEnd !== 0 && (abs($instance) - 1 > ceil(count($split) / 2))) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
|
||||
return $split;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array|string $delimiter the text that marks the point before which you want to extract
|
||||
* Multiple delimiters can be passed as an array of string values
|
||||
*/
|
||||
private static function buildDelimiter($delimiter): string
|
||||
{
|
||||
if (is_array($delimiter)) {
|
||||
$delimiter = Functions::flattenArray($delimiter);
|
||||
$quotedDelimiters = array_map(
|
||||
fn ($delimiter): string => preg_quote($delimiter ?? '', '/'),
|
||||
$delimiter
|
||||
);
|
||||
$delimiters = implode('|', $quotedDelimiters);
|
||||
|
||||
return '(' . $delimiters . ')';
|
||||
}
|
||||
|
||||
return '(' . preg_quote($delimiter ?? '', '/') . ')';
|
||||
}
|
||||
|
||||
private static function matchFlags(int $matchMode): string
|
||||
{
|
||||
return ($matchMode === 0) ? 'mu' : 'miu';
|
||||
}
|
||||
}
|
||||
308
lib/PhpSpreadsheet/Calculation/TextData/Format.php
Normal file
308
lib/PhpSpreadsheet/Calculation/TextData/Format.php
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
|
||||
|
||||
use DateTimeInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
use PhpOffice\PhpSpreadsheet\RichText\RichText;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
|
||||
class Format
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* DOLLAR.
|
||||
*
|
||||
* This function converts a number to text using currency format, with the decimals rounded to the specified place.
|
||||
* The format used is $#,##0.00_);($#,##0.00)..
|
||||
*
|
||||
* @param mixed $value The value to format
|
||||
* Or can be an array of values
|
||||
* @param mixed $decimals The number of digits to display to the right of the decimal point (as an integer).
|
||||
* If decimals is negative, number is rounded to the left of the decimal point.
|
||||
* If you omit decimals, it is assumed to be 2
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed for either of the arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function DOLLAR(mixed $value = 0, mixed $decimals = 2)
|
||||
{
|
||||
if (is_array($value) || is_array($decimals)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimals);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = Helpers::extractFloat($value);
|
||||
$decimals = Helpers::extractInt($decimals, -100, 0, true);
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$mask = '$#,##0';
|
||||
if ($decimals > 0) {
|
||||
$mask .= '.' . str_repeat('0', $decimals);
|
||||
} else {
|
||||
$round = 10 ** abs($decimals);
|
||||
if ($value < 0) {
|
||||
$round = 0 - $round;
|
||||
}
|
||||
$value = MathTrig\Round::multiple($value, $round);
|
||||
}
|
||||
$mask = "{$mask};-{$mask}";
|
||||
|
||||
return NumberFormat::toFormattedString($value, $mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXED.
|
||||
*
|
||||
* @param mixed $value The value to format
|
||||
* Or can be an array of values
|
||||
* @param mixed $decimals Integer value for the number of decimal places that should be formatted
|
||||
* Or can be an array of values
|
||||
* @param mixed $noCommas Boolean value indicating whether the value should have thousands separators or not
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed for either of the arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function FIXEDFORMAT(mixed $value, mixed $decimals = 2, mixed $noCommas = false): array|string
|
||||
{
|
||||
if (is_array($value) || is_array($decimals) || is_array($noCommas)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimals, $noCommas);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = Helpers::extractFloat($value);
|
||||
$decimals = Helpers::extractInt($decimals, -100, 0, true);
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$valueResult = round($value, $decimals);
|
||||
if ($decimals < 0) {
|
||||
$decimals = 0;
|
||||
}
|
||||
if ($noCommas === false) {
|
||||
$valueResult = number_format(
|
||||
$valueResult,
|
||||
$decimals,
|
||||
StringHelper::getDecimalSeparator(),
|
||||
StringHelper::getThousandsSeparator()
|
||||
);
|
||||
}
|
||||
|
||||
return (string) $valueResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* TEXT.
|
||||
*
|
||||
* @param mixed $value The value to format
|
||||
* Or can be an array of values
|
||||
* @param mixed $format A string with the Format mask that should be used
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed for either of the arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function TEXTFORMAT(mixed $value, mixed $format): array|string
|
||||
{
|
||||
if (is_array($value) || is_array($format)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format);
|
||||
}
|
||||
|
||||
$value = Helpers::extractString($value);
|
||||
$format = Helpers::extractString($format);
|
||||
|
||||
if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) {
|
||||
$value = DateTimeExcel\DateValue::fromString($value) + DateTimeExcel\TimeValue::fromString($value);
|
||||
}
|
||||
|
||||
return (string) NumberFormat::toFormattedString($value, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value Value to check
|
||||
*/
|
||||
private static function convertValue(mixed $value, bool $spacesMeanZero = false): mixed
|
||||
{
|
||||
$value = $value ?? 0;
|
||||
if (is_bool($value)) {
|
||||
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
|
||||
$value = (int) $value;
|
||||
} else {
|
||||
throw new CalcExp(ExcelError::VALUE());
|
||||
}
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$value = trim($value);
|
||||
if ($spacesMeanZero && $value === '') {
|
||||
$value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* VALUE.
|
||||
*
|
||||
* @param mixed $value Value to check
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|DateTimeInterface|float|int|string A string if arguments are invalid
|
||||
* If an array of values is passed for the argument, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function VALUE(mixed $value = '')
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = self::convertValue($value);
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
if (!is_numeric($value)) {
|
||||
$numberValue = str_replace(
|
||||
StringHelper::getThousandsSeparator(),
|
||||
'',
|
||||
trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode())
|
||||
);
|
||||
if ($numberValue === '') {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
if (is_numeric($numberValue)) {
|
||||
return (float) $numberValue;
|
||||
}
|
||||
|
||||
$dateSetting = Functions::getReturnDateType();
|
||||
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
|
||||
|
||||
if (str_contains($value, ':')) {
|
||||
$timeValue = Functions::scalar(DateTimeExcel\TimeValue::fromString($value));
|
||||
if ($timeValue !== ExcelError::VALUE()) {
|
||||
Functions::setReturnDateType($dateSetting);
|
||||
|
||||
return $timeValue;
|
||||
}
|
||||
}
|
||||
$dateValue = Functions::scalar(DateTimeExcel\DateValue::fromString($value));
|
||||
if ($dateValue !== ExcelError::VALUE()) {
|
||||
Functions::setReturnDateType($dateSetting);
|
||||
|
||||
return $dateValue;
|
||||
}
|
||||
Functions::setReturnDateType($dateSetting);
|
||||
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
return (float) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* TEXT.
|
||||
*
|
||||
* @param mixed $value The value to format
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed for either of the arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function valueToText(mixed $value, mixed $format = false): array|string
|
||||
{
|
||||
if (is_array($value) || is_array($format)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format);
|
||||
}
|
||||
|
||||
$format = (bool) $format;
|
||||
|
||||
if (is_object($value) && $value instanceof RichText) {
|
||||
$value = $value->getPlainText();
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$value = ($format === true) ? Calculation::wrapResult($value) : $value;
|
||||
$value = str_replace("\n", '', $value);
|
||||
} elseif (is_bool($value)) {
|
||||
$value = Calculation::getLocaleBoolean($value ? 'TRUE' : 'FALSE');
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
private static function getDecimalSeparator(mixed $decimalSeparator): string
|
||||
{
|
||||
return empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : (string) $decimalSeparator;
|
||||
}
|
||||
|
||||
private static function getGroupSeparator(mixed $groupSeparator): string
|
||||
{
|
||||
return empty($groupSeparator) ? StringHelper::getThousandsSeparator() : (string) $groupSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* NUMBERVALUE.
|
||||
*
|
||||
* @param mixed $value The value to format
|
||||
* Or can be an array of values
|
||||
* @param mixed $decimalSeparator A string with the decimal separator to use, defaults to locale defined value
|
||||
* Or can be an array of values
|
||||
* @param mixed $groupSeparator A string with the group/thousands separator to use, defaults to locale defined value
|
||||
* Or can be an array of values
|
||||
*/
|
||||
public static function NUMBERVALUE(mixed $value = '', mixed $decimalSeparator = null, mixed $groupSeparator = null): array|string|float
|
||||
{
|
||||
if (is_array($value) || is_array($decimalSeparator) || is_array($groupSeparator)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimalSeparator, $groupSeparator);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = self::convertValue($value, true);
|
||||
$decimalSeparator = self::getDecimalSeparator($decimalSeparator);
|
||||
$groupSeparator = self::getGroupSeparator($groupSeparator);
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (!is_numeric($value)) {
|
||||
$decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator, '/') . '/', $value, $matches, PREG_OFFSET_CAPTURE);
|
||||
if ($decimalPositions > 1) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
$decimalOffset = array_pop($matches[0])[1] ?? null;
|
||||
if ($decimalOffset === null || strpos($value, $groupSeparator, $decimalOffset) !== false) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
$value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value);
|
||||
|
||||
// Handle the special case of trailing % signs
|
||||
$percentageString = rtrim($value, '%');
|
||||
if (!is_numeric($percentageString)) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
$percentageAdjustment = strlen($value) - strlen($percentageString);
|
||||
if ($percentageAdjustment) {
|
||||
$value = (float) $percentageString;
|
||||
$value /= 10 ** ($percentageAdjustment * 2);
|
||||
}
|
||||
}
|
||||
|
||||
return is_array($value) ? ExcelError::VALUE() : (float) $value;
|
||||
}
|
||||
}
|
||||
82
lib/PhpSpreadsheet/Calculation/TextData/Helpers.php
Normal file
82
lib/PhpSpreadsheet/Calculation/TextData/Helpers.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Helpers
|
||||
{
|
||||
public static function convertBooleanValue(bool $value): string
|
||||
{
|
||||
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value String value from which to extract characters
|
||||
*/
|
||||
public static function extractString(mixed $value, bool $throwIfError = false): string
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return self::convertBooleanValue($value);
|
||||
}
|
||||
if ($throwIfError && is_string($value) && ErrorValue::isError($value)) {
|
||||
throw new CalcExp($value);
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
public static function extractInt(mixed $value, int $minValue, int $gnumericNull = 0, bool $ooBoolOk = false): int
|
||||
{
|
||||
if ($value === null) {
|
||||
// usually 0, but sometimes 1 for Gnumeric
|
||||
$value = (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_GNUMERIC) ? $gnumericNull : 0;
|
||||
}
|
||||
if (is_bool($value) && ($ooBoolOk || Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE)) {
|
||||
$value = (int) $value;
|
||||
}
|
||||
if (!is_numeric($value)) {
|
||||
throw new CalcExp(ExcelError::VALUE());
|
||||
}
|
||||
$value = (int) $value;
|
||||
if ($value < $minValue) {
|
||||
throw new CalcExp(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
public static function extractFloat(mixed $value): float
|
||||
{
|
||||
if ($value === null) {
|
||||
$value = 0.0;
|
||||
}
|
||||
if (is_bool($value)) {
|
||||
$value = (float) $value;
|
||||
}
|
||||
if (!is_numeric($value)) {
|
||||
throw new CalcExp(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return (float) $value;
|
||||
}
|
||||
|
||||
public static function validateInt(mixed $value): int
|
||||
{
|
||||
if ($value === null) {
|
||||
$value = 0;
|
||||
} elseif (is_bool($value)) {
|
||||
$value = (int) $value;
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
116
lib/PhpSpreadsheet/Calculation/TextData/Replace.php
Normal file
116
lib/PhpSpreadsheet/Calculation/TextData/Replace.php
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class Replace
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* REPLACE.
|
||||
*
|
||||
* @param mixed $oldText The text string value to modify
|
||||
* Or can be an array of values
|
||||
* @param mixed $start Integer offset for start character of the replacement
|
||||
* Or can be an array of values
|
||||
* @param mixed $chars Integer number of characters to replace from the start offset
|
||||
* Or can be an array of values
|
||||
* @param mixed $newText String to replace in the defined position
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed for either of the arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function replace(mixed $oldText, mixed $start, mixed $chars, mixed $newText): array|string
|
||||
{
|
||||
if (is_array($oldText) || is_array($start) || is_array($chars) || is_array($newText)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $oldText, $start, $chars, $newText);
|
||||
}
|
||||
|
||||
try {
|
||||
$start = Helpers::extractInt($start, 1, 0, true);
|
||||
$chars = Helpers::extractInt($chars, 0, 0, true);
|
||||
$oldText = Helpers::extractString($oldText, true);
|
||||
$newText = Helpers::extractString($newText, true);
|
||||
$left = StringHelper::substring($oldText, 0, $start - 1);
|
||||
|
||||
$right = StringHelper::substring($oldText, $start + $chars - 1, null);
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$returnValue = $left . $newText . $right;
|
||||
if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) {
|
||||
$returnValue = ExcelError::VALUE();
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* SUBSTITUTE.
|
||||
*
|
||||
* @param mixed $text The text string value to modify
|
||||
* Or can be an array of values
|
||||
* @param mixed $fromText The string value that we want to replace in $text
|
||||
* Or can be an array of values
|
||||
* @param mixed $toText The string value that we want to replace with in $text
|
||||
* Or can be an array of values
|
||||
* @param mixed $instance Integer instance Number for the occurrence of frmText to change
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed for either of the arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function substitute(mixed $text = '', mixed $fromText = '', mixed $toText = '', mixed $instance = null): array|string
|
||||
{
|
||||
if (is_array($text) || is_array($fromText) || is_array($toText) || is_array($instance)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $text, $fromText, $toText, $instance);
|
||||
}
|
||||
|
||||
try {
|
||||
$text = Helpers::extractString($text, true);
|
||||
$fromText = Helpers::extractString($fromText, true);
|
||||
$toText = Helpers::extractString($toText, true);
|
||||
if ($instance === null) {
|
||||
$returnValue = str_replace($fromText, $toText, $text);
|
||||
} else {
|
||||
if (is_bool($instance)) {
|
||||
if ($instance === false || Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
|
||||
return ExcelError::Value();
|
||||
}
|
||||
$instance = 1;
|
||||
}
|
||||
$instance = Helpers::extractInt($instance, 1, 0, true);
|
||||
$returnValue = self::executeSubstitution($text, $fromText, $toText, $instance);
|
||||
}
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) {
|
||||
$returnValue = ExcelError::VALUE();
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
private static function executeSubstitution(string $text, string $fromText, string $toText, int $instance): string
|
||||
{
|
||||
$pos = -1;
|
||||
while ($instance > 0) {
|
||||
$pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8');
|
||||
if ($pos === false) {
|
||||
return $text;
|
||||
}
|
||||
--$instance;
|
||||
}
|
||||
|
||||
return Functions::scalar(self::REPLACE($text, ++$pos, StringHelper::countCharacters($fromText), $toText));
|
||||
}
|
||||
}
|
||||
97
lib/PhpSpreadsheet/Calculation/TextData/Search.php
Normal file
97
lib/PhpSpreadsheet/Calculation/TextData/Search.php
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class Search
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* FIND (case sensitive search).
|
||||
*
|
||||
* @param mixed $needle The string to look for
|
||||
* Or can be an array of values
|
||||
* @param mixed $haystack The string in which to look
|
||||
* Or can be an array of values
|
||||
* @param mixed $offset Integer offset within $haystack to start searching from
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|int|string The offset where the first occurrence of needle was found in the haystack
|
||||
* If an array of values is passed for the $value or $chars arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function sensitive(mixed $needle, mixed $haystack, mixed $offset = 1): array|string|int
|
||||
{
|
||||
if (is_array($needle) || is_array($haystack) || is_array($offset)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $needle, $haystack, $offset);
|
||||
}
|
||||
|
||||
try {
|
||||
$needle = Helpers::extractString($needle);
|
||||
$haystack = Helpers::extractString($haystack);
|
||||
$offset = Helpers::extractInt($offset, 1, 0, true);
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (StringHelper::countCharacters($haystack) >= $offset) {
|
||||
if (StringHelper::countCharacters($needle) === 0) {
|
||||
return $offset;
|
||||
}
|
||||
|
||||
$pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8');
|
||||
if ($pos !== false) {
|
||||
return ++$pos;
|
||||
}
|
||||
}
|
||||
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
/**
|
||||
* SEARCH (case insensitive search).
|
||||
*
|
||||
* @param mixed $needle The string to look for
|
||||
* Or can be an array of values
|
||||
* @param mixed $haystack The string in which to look
|
||||
* Or can be an array of values
|
||||
* @param mixed $offset Integer offset within $haystack to start searching from
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|int|string The offset where the first occurrence of needle was found in the haystack
|
||||
* If an array of values is passed for the $value or $chars arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function insensitive(mixed $needle, mixed $haystack, mixed $offset = 1): array|string|int
|
||||
{
|
||||
if (is_array($needle) || is_array($haystack) || is_array($offset)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $needle, $haystack, $offset);
|
||||
}
|
||||
|
||||
try {
|
||||
$needle = Helpers::extractString($needle);
|
||||
$haystack = Helpers::extractString($haystack);
|
||||
$offset = Helpers::extractInt($offset, 1, 0, true);
|
||||
} catch (CalcExp $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (StringHelper::countCharacters($haystack) >= $offset) {
|
||||
if (StringHelper::countCharacters($needle) === 0) {
|
||||
return $offset;
|
||||
}
|
||||
|
||||
$pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8');
|
||||
if ($pos !== false) {
|
||||
return ++$pos;
|
||||
}
|
||||
}
|
||||
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
}
|
||||
233
lib/PhpSpreadsheet/Calculation/TextData/Text.php
Normal file
233
lib/PhpSpreadsheet/Calculation/TextData/Text.php
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
|
||||
|
||||
class Text
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* LEN.
|
||||
*
|
||||
* @param mixed $value String Value
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|int If an array of values is passed for the argument, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function length(mixed $value = ''): array|int
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
|
||||
}
|
||||
|
||||
$value = Helpers::extractString($value);
|
||||
|
||||
return mb_strlen($value, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two text strings and returns TRUE if they are exactly the same, FALSE otherwise.
|
||||
* EXACT is case-sensitive but ignores formatting differences.
|
||||
* Use EXACT to test text being entered into a document.
|
||||
*
|
||||
* @param mixed $value1 String Value
|
||||
* Or can be an array of values
|
||||
* @param mixed $value2 String Value
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|bool If an array of values is passed for either of the arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function exact(mixed $value1, mixed $value2): array|bool
|
||||
{
|
||||
if (is_array($value1) || is_array($value2)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value1, $value2);
|
||||
}
|
||||
|
||||
$value1 = Helpers::extractString($value1);
|
||||
$value2 = Helpers::extractString($value2);
|
||||
|
||||
return $value2 === $value1;
|
||||
}
|
||||
|
||||
/**
|
||||
* T.
|
||||
*
|
||||
* @param mixed $testValue Value to check
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed for the argument, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function test(mixed $testValue = ''): array|string
|
||||
{
|
||||
if (is_array($testValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $testValue);
|
||||
}
|
||||
|
||||
if (is_string($testValue)) {
|
||||
return $testValue;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* TEXTSPLIT.
|
||||
*
|
||||
* @param mixed $text the text that you're searching
|
||||
* @param null|array|string $columnDelimiter The text that marks the point where to spill the text across columns.
|
||||
* Multiple delimiters can be passed as an array of string values
|
||||
* @param null|array|string $rowDelimiter The text that marks the point where to spill the text down rows.
|
||||
* Multiple delimiters can be passed as an array of string values
|
||||
* @param bool $ignoreEmpty Specify FALSE to create an empty cell when two delimiters are consecutive.
|
||||
* true = create empty cells
|
||||
* false = skip empty cells
|
||||
* Defaults to TRUE, which creates an empty cell
|
||||
* @param bool $matchMode Determines whether the match is case-sensitive or not.
|
||||
* true = case-sensitive
|
||||
* false = case-insensitive
|
||||
* By default, a case-sensitive match is done.
|
||||
* @param mixed $padding The value with which to pad the result.
|
||||
* The default is #N/A.
|
||||
*
|
||||
* @return array the array built from the text, split by the row and column delimiters
|
||||
*/
|
||||
public static function split(mixed $text, $columnDelimiter = null, $rowDelimiter = null, bool $ignoreEmpty = false, bool $matchMode = true, mixed $padding = '#N/A'): array
|
||||
{
|
||||
$text = Functions::flattenSingleValue($text);
|
||||
|
||||
$flags = self::matchFlags($matchMode);
|
||||
|
||||
if ($rowDelimiter !== null) {
|
||||
$delimiter = self::buildDelimiter($rowDelimiter);
|
||||
$rows = ($delimiter === '()')
|
||||
? [$text]
|
||||
: preg_split("/{$delimiter}/{$flags}", $text);
|
||||
} else {
|
||||
$rows = [$text];
|
||||
}
|
||||
|
||||
/** @var array $rows */
|
||||
if ($ignoreEmpty === true) {
|
||||
$rows = array_values(array_filter(
|
||||
$rows,
|
||||
fn ($row): bool => $row !== ''
|
||||
));
|
||||
}
|
||||
|
||||
if ($columnDelimiter !== null) {
|
||||
$delimiter = self::buildDelimiter($columnDelimiter);
|
||||
array_walk(
|
||||
$rows,
|
||||
function (&$row) use ($delimiter, $flags, $ignoreEmpty): void {
|
||||
$row = ($delimiter === '()')
|
||||
? [$row]
|
||||
: preg_split("/{$delimiter}/{$flags}", $row);
|
||||
/** @var array $row */
|
||||
if ($ignoreEmpty === true) {
|
||||
$row = array_values(array_filter(
|
||||
$row,
|
||||
fn ($value): bool => $value !== ''
|
||||
));
|
||||
}
|
||||
}
|
||||
);
|
||||
if ($ignoreEmpty === true) {
|
||||
$rows = array_values(array_filter(
|
||||
$rows,
|
||||
fn ($row): bool => $row !== [] && $row !== ['']
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return self::applyPadding($rows, $padding);
|
||||
}
|
||||
|
||||
private static function applyPadding(array $rows, mixed $padding): array
|
||||
{
|
||||
$columnCount = array_reduce(
|
||||
$rows,
|
||||
fn (int $counter, array $row): int => max($counter, count($row)),
|
||||
0
|
||||
);
|
||||
|
||||
return array_map(
|
||||
function (array $row) use ($columnCount, $padding): array {
|
||||
return (count($row) < $columnCount)
|
||||
? array_merge($row, array_fill(0, $columnCount - count($row), $padding))
|
||||
: $row;
|
||||
},
|
||||
$rows
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array|string $delimiter the text that marks the point before which you want to split
|
||||
* Multiple delimiters can be passed as an array of string values
|
||||
*/
|
||||
private static function buildDelimiter($delimiter): string
|
||||
{
|
||||
$valueSet = Functions::flattenArray($delimiter);
|
||||
|
||||
if (is_array($delimiter) && count($valueSet) > 1) {
|
||||
$quotedDelimiters = array_map(
|
||||
fn ($delimiter): string => preg_quote($delimiter ?? '', '/'),
|
||||
$valueSet
|
||||
);
|
||||
$delimiters = implode('|', $quotedDelimiters);
|
||||
|
||||
return '(' . $delimiters . ')';
|
||||
}
|
||||
|
||||
return '(' . preg_quote(Functions::flattenSingleValue($delimiter), '/') . ')';
|
||||
}
|
||||
|
||||
private static function matchFlags(bool $matchMode): string
|
||||
{
|
||||
return ($matchMode === true) ? 'miu' : 'mu';
|
||||
}
|
||||
|
||||
public static function fromArray(array $array, int $format = 0): string
|
||||
{
|
||||
$result = [];
|
||||
foreach ($array as $row) {
|
||||
$cells = [];
|
||||
foreach ($row as $cellValue) {
|
||||
$value = ($format === 1) ? self::formatValueMode1($cellValue) : self::formatValueMode0($cellValue);
|
||||
$cells[] = $value;
|
||||
}
|
||||
$result[] = implode(($format === 1) ? ',' : ', ', $cells);
|
||||
}
|
||||
|
||||
$result = implode(($format === 1) ? ';' : ', ', $result);
|
||||
|
||||
return ($format === 1) ? '{' . $result . '}' : $result;
|
||||
}
|
||||
|
||||
private static function formatValueMode0(mixed $cellValue): string
|
||||
{
|
||||
if (is_bool($cellValue)) {
|
||||
return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE');
|
||||
}
|
||||
|
||||
return (string) $cellValue;
|
||||
}
|
||||
|
||||
private static function formatValueMode1(mixed $cellValue): string
|
||||
{
|
||||
if (is_string($cellValue) && ErrorValue::isError($cellValue) === false) {
|
||||
return Calculation::FORMULA_STRING_QUOTE . $cellValue . Calculation::FORMULA_STRING_QUOTE;
|
||||
} elseif (is_bool($cellValue)) {
|
||||
return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE');
|
||||
}
|
||||
|
||||
return (string) $cellValue;
|
||||
}
|
||||
}
|
||||
50
lib/PhpSpreadsheet/Calculation/TextData/Trim.php
Normal file
50
lib/PhpSpreadsheet/Calculation/TextData/Trim.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
|
||||
class Trim
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* CLEAN.
|
||||
*
|
||||
* @param mixed $stringValue String Value to check
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed as the argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function nonPrintable(mixed $stringValue = '')
|
||||
{
|
||||
if (is_array($stringValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $stringValue);
|
||||
}
|
||||
|
||||
$stringValue = Helpers::extractString($stringValue);
|
||||
|
||||
return (string) preg_replace('/[\\x00-\\x1f]/', '', "$stringValue");
|
||||
}
|
||||
|
||||
/**
|
||||
* TRIM.
|
||||
*
|
||||
* @param mixed $stringValue String Value to check
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|string If an array of values is passed as the argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function spaces(mixed $stringValue = ''): array|string
|
||||
{
|
||||
if (is_array($stringValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $stringValue);
|
||||
}
|
||||
|
||||
$stringValue = Helpers::extractString($stringValue);
|
||||
|
||||
return trim(preg_replace('/ +/', ' ', trim("$stringValue", ' ')) ?? '', ' ');
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue