Allow Replace of Dummy Function with Custom Function

Custom functions were introduced recently, with a restriction that non-custom functions could not be replaced. However, reviewing a recent issue has led me to the conclusion that it might sometimes be impractical to implement an Excel function in PhpSpreadsheet for all users, but it could still be helpful to some users to offer an implementation anyhow.

The Excel ASC function is currently unimplemented in PhpSpreadsheet, and is a candidate for replacement using the functionality added in this PR. This PR supersedes PR #4513, which I will now close, and its earlier incarnation PR #4511. For reasons discussed in 4513, I don't see a way forward for implementing the Excel ASC function in a way that would be generally usable. However, this PR would permit a user to implement ASC in a way which would satisfy the user's local requirements. See the new `testReplaceDummyFunction`; I think, or at least hope, that, despite the deficiencies of 4511 and 4513, this might satisfy the author's requirement.
This commit is contained in:
oleibman
2025-07-18 22:01:59 -07:00
parent 747ccd1b44
commit f1477b130f
3 changed files with 55 additions and 1 deletions
@@ -30,7 +30,10 @@ class CalculationBase
public static function addFunction(string $key, array $value): bool
{
$key = strtoupper($key);
if (array_key_exists($key, FunctionArray::$phpSpreadsheetFunctions)) {
if (
array_key_exists($key, FunctionArray::$phpSpreadsheetFunctions)
&& !self::isDummy($key)
) {
return false;
}
$value['custom'] = true;
@@ -39,6 +42,20 @@ class CalculationBase
return true;
}
private static function isDummy(string $key): bool
{
// key is already known to exist
$functionCall = FunctionArray::$phpSpreadsheetFunctions[$key]['functionCall'] ?? null;
if (!is_array($functionCall)) {
return false;
}
if (($functionCall[1] ?? '') !== 'DUMMY') {
return false;
}
return true;
}
public static function removeFunction(string $key): bool
{
$key = strtoupper($key);
@@ -6,6 +6,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Helpers;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class CustomFunction
{
@@ -19,4 +20,25 @@ class CustomFunction
return $number ** 4;
}
/**
* ASC.
* Converts full-width (double-byte) characters to half-width (single-byte) characters.
* There are many difficulties with implementing this into PhpSpreadsheet.
*/
public static function ASC(mixed $stringValue): string
{
/*if (is_array($stringValue)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $stringValue);
}*/
$stringValue = StringHelper::convertToString($stringValue, convertBool: true);
if (function_exists('mb_convert_kana')) {
return mb_convert_kana($stringValue, 'a', 'UTF-8');
}
// Fallback if mb_convert_kana is not available.
// PhpSpreadsheet heavily relies on mbstring, so this is more of a theoretical fallback.
// A comprehensive manual conversion is extensive.
return $stringValue;
}
}
@@ -31,4 +31,19 @@ class CustomFunctionTest extends TestCase
self::assertSame('#NAME?', $calculation->calculateFormula('=FOURTHPOWER(3)'));
self::assertFalse(Calculation::removeFunction('WHATEVER'));
}
public static function testReplaceDummyFunction(): void
{
$functions = Calculation::getFunctions();
$key = 'ASC';
$oldValue = $functions[$key] ?? null;
self::assertIsArray($oldValue);
$calculation = Calculation::getInstance();
$value = $oldValue;
$value['functionCall'] = [CustomFunction::class, 'ASC'];
self::assertTrue(Calculation::addFunction($key, $value));
self::assertSame('ABC', $calculation->calculateFormula('=ASC("ABC")'));
self::assertTrue(Calculation::removeFunction('ASC'));
self::assertTrue(Calculation::addFunction($key, $oldValue));
}
}