Merge pull request #2972 from PHPOffice/TextFunctions-New

Allow multiple delimiters for `TEXTBEFORE()` and `TEXTAFTER()` functions
This commit is contained in:
Mark Baker 2022-07-30 10:57:52 +02:00 committed by GitHub
commit 1c587e90c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 119 additions and 31 deletions

View File

@ -104,7 +104,8 @@ class Extract
*
* @param mixed $text the text that you're searching
* Or can be an array of values
* @param ?string $delimiter the text that marks the point before which you want to extract
* @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.
@ -132,7 +133,6 @@ class Extract
}
$text = Helpers::extractString($text ?? '');
$delimiter = Helpers::extractString(Functions::flattenSingleValue($delimiter ?? ''));
$instance = (int) $instance;
$matchMode = (int) $matchMode;
$matchEnd = (int) $matchEnd;
@ -141,13 +141,14 @@ class Extract
if (is_array($split) === false) {
return $split;
}
if ($delimiter === '') {
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);
$adjust = preg_match('/^' . preg_quote($delimiter) . "\$/{$flags}", $split[0]);
$delimiter = self::buildDelimiter($delimiter);
$adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]);
$oddReverseAdjustment = count($split) % 2;
$split = ($instance < 0)
@ -161,7 +162,8 @@ class Extract
* TEXTAFTER.
*
* @param mixed $text the text that you're searching
* @param ?string $delimiter the text that marks the point before which you want to extract
* @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.
@ -189,7 +191,6 @@ class Extract
}
$text = Helpers::extractString($text ?? '');
$delimiter = Helpers::extractString(Functions::flattenSingleValue($delimiter ?? ''));
$instance = (int) $instance;
$matchMode = (int) $matchMode;
$matchEnd = (int) $matchEnd;
@ -198,13 +199,14 @@ class Extract
if (is_array($split) === false) {
return $split;
}
if ($delimiter === '') {
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);
$adjust = preg_match('/^' . preg_quote($delimiter) . "\$/{$flags}", $split[0]);
$delimiter = self::buildDelimiter($delimiter);
$adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]);
$oddReverseAdjustment = count($split) % 2;
$split = ($instance < 0)
@ -215,21 +217,23 @@ class Extract
}
/**
* @param null|array|string $delimiter
* @param int $matchMode
* @param int $matchEnd
* @param mixed $ifNotFound
*
* @return string|string[]
*/
private static function validateTextBeforeAfter(string $text, string $delimiter, int $instance, $matchMode, $matchEnd, $ifNotFound)
private static function validateTextBeforeAfter(string $text, $delimiter, int $instance, $matchMode, $matchEnd, $ifNotFound)
{
$flags = self::matchFlags($matchMode);
$delimiter = self::buildDelimiter($delimiter);
if (preg_match('/' . preg_quote($delimiter) . "/{$flags}", $text) === 0 && $matchEnd === 0) {
if (preg_match('/' . $delimiter . "/{$flags}", $text) === 0 && $matchEnd === 0) {
return $ifNotFound;
}
$split = preg_split('/(' . preg_quote($delimiter) . ")/{$flags}", $text, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$split = preg_split('/' . $delimiter . "/{$flags}", $text, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
if ($split === false) {
return ExcelError::NA();
}
@ -247,6 +251,28 @@ class Extract
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(
function ($delimiter) {
return 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';

View File

@ -2,8 +2,6 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
class TextAfterTest extends AllSetupTeardown
{
/**
@ -14,14 +12,17 @@ class TextAfterTest extends AllSetupTeardown
$text = $arguments[0];
$delimiter = $arguments[1];
$args = 'A1, A2';
$args = (is_array($delimiter)) ? 'A1, {A2,A3}' : 'A1, A2';
$args .= (isset($arguments[2])) ? ", {$arguments[2]}" : ',';
$args .= (isset($arguments[3])) ? ", {$arguments[3]}" : ',';
$args .= (isset($arguments[4])) ? ", {$arguments[4]}" : ',';
$worksheet = $this->getSheet();
$worksheet->getCell('A1')->setValue($text);
$worksheet->getCell('A2')->setValue($delimiter);
$worksheet->getCell('A2')->setValue((is_array($delimiter)) ? $delimiter[0] : $delimiter);
if (is_array($delimiter)) {
$worksheet->getCell('A3')->setValue($delimiter[1]);
}
$worksheet->getCell('B1')->setValue("=TEXTAFTER({$args})");
$result = $worksheet->getCell('B1')->getCalculatedValue();
@ -32,18 +33,4 @@ class TextAfterTest extends AllSetupTeardown
{
return require 'tests/data/Calculation/TextData/TEXTAFTER.php';
}
public function testTextAfterWithArray(): void
{
$calculation = Calculation::getInstance();
$text = "Red Riding Hood's red riding hood";
$delimiter = 'red';
$args = "\"{$text}\", \"{$delimiter}\", 1, {0;1}";
$formula = "=TEXTAFTER({$args})";
$result = $calculation->_calculateFormulaValue($formula);
self::assertEquals([[' riding hood'], [" Riding Hood's red riding hood"]], $result);
}
}

View File

@ -12,14 +12,17 @@ class TextBeforeTest extends AllSetupTeardown
$text = $arguments[0];
$delimiter = $arguments[1];
$args = 'A1, A2';
$args = (is_array($delimiter)) ? 'A1, {A2,A3}' : 'A1, A2';
$args .= (isset($arguments[2])) ? ", {$arguments[2]}" : ',';
$args .= (isset($arguments[3])) ? ", {$arguments[3]}" : ',';
$args .= (isset($arguments[4])) ? ", {$arguments[4]}" : ',';
$worksheet = $this->getSheet();
$worksheet->getCell('A1')->setValue($text);
$worksheet->getCell('A2')->setValue($delimiter);
$worksheet->getCell('A2')->setValue((is_array($delimiter)) ? $delimiter[0] : $delimiter);
if (is_array($delimiter)) {
$worksheet->getCell('A3')->setValue($delimiter[1]);
}
$worksheet->getCell('B1')->setValue("=TEXTBEFORE({$args})");
$result = $worksheet->getCell('B1')->getCalculatedValue();

View File

@ -212,4 +212,40 @@ return [
1,
],
],
'Multi-delimiter Case-Insensitive Offset 1' => [
" riding hood's red riding hood",
[
"Little Red riding hood's red riding hood",
['HOOD', 'RED'],
1,
1,
],
],
'Multi-delimiter Case-Insensitive Offset 2' => [
"'s red riding hood",
[
"Little Red riding hood's red riding hood",
['HOOD', 'RED'],
2,
1,
],
],
'Multi-delimiter Case-Insensitive Offset 3' => [
' riding hood',
[
"Little Red riding hood's red riding hood",
['HOOD', 'RED'],
3,
1,
],
],
'Multi-delimiter Case-Insensitive Offset -2' => [
' riding hood',
[
"Little Red riding hood's red riding hood",
['HOOD', 'RED'],
-2,
1,
],
],
];

View File

@ -204,4 +204,40 @@ return [
1,
],
],
'Multi-delimiter Case-Insensitive Offset 1' => [
'Little ',
[
"Little Red riding hood's red riding hood",
['HOOD', 'RED'],
1,
1,
],
],
'Multi-delimiter Case-Insensitive Offset 2' => [
'Little Red riding ',
[
"Little Red riding hood's red riding hood",
['HOOD', 'RED'],
2,
1,
],
],
'Multi-delimiter Case-Insensitive Offset 3' => [
"Little Red riding hood's ",
[
"Little Red riding hood's red riding hood",
['HOOD', 'RED'],
3,
1,
],
],
'Multi-delimiter Case-Insensitive Offset -2' => [
"Little Red riding hood's ",
[
"Little Red riding hood's red riding hood",
['HOOD', 'RED'],
-2,
1,
],
],
];