Use WildcardMatch

Per suggestion from @MarkBaker.

WildcardMatch did not handle double tilde correctly. It has been changed to do so and its logic simplified (and commented).

Existing AutoFilter test covered this situation, but I added a test for MATCH as well.
This commit is contained in:
Owen Leibman 2021-06-16 07:42:32 -07:00 committed by Mark Baker
parent d88af46ab5
commit d0dd5b4594
4 changed files with 22 additions and 33 deletions

View File

@ -755,16 +755,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Internal\\\\WildcardMatch\\:\\:wildcard\\(\\) should return string but returns string\\|null\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Internal\\\\WildcardMatch\\:\\:compare\\(\\) has parameter \\$value with no typehint specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php
-
message: "#^Call to function is_string\\(\\) with null will always evaluate to false\\.$#"
count: 3

View File

@ -5,30 +5,30 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Internal;
class WildcardMatch
{
private const SEARCH_SET = [
'/(?<!~)\*/ui',
'/~\*/ui',
'/(?<!~)\?/ui',
'/~\?/ui',
'~~', // convert double tilde to unprintable value
'~\\*', // convert tilde backslash asterisk to [*] (matches literal asterisk in regexp)
'\\*', // convert backslash asterisk to .* (matches string of any length in regexp)
'~\\?', // convert tilde backslash question to [?] (matches literal question mark in regexp)
'\\?', // convert backslash question to . (matches one character in regexp)
"\x1c", // convert original double tilde to single tilde
];
private const REPLACEMENT_SET = [
'${1}.*',
'\*',
'${1}.',
'\?',
"\x1c",
'[*]',
'.*',
'[?]',
'.',
'~',
];
public static function wildcard(string $wildcard): string
{
// Preg Escape the wildcard, but protecting the Excel * and ? search characters
$wildcard = str_replace(['*', '?'], [0x1A, 0x1B], $wildcard);
$wildcard = preg_quote($wildcard);
$wildcard = str_replace([0x1A, 0x1B], ['*', '?'], $wildcard);
return preg_replace(self::SEARCH_SET, self::REPLACEMENT_SET, $wildcard);
return str_replace(self::SEARCH_SET, self::REPLACEMENT_SET, preg_quote($wildcard));
}
public static function compare($value, string $wildcard): bool
public static function compare(string $value, string $wildcard): bool
{
if ($value === '') {
return true;

View File

@ -6,6 +6,7 @@ use DateTime;
use DateTimeZone;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Shared\Date;
@ -452,13 +453,6 @@ class AutoFilter
return false;
}
/**
* Search/Replace arrays to convert Excel wildcard syntax to a regexp syntax for preg_matching.
*/
private const FROM_REPLACE = ['~~', '~\\*', '\\*', '~\\?', '\\?', "\x1c"];
private const TO_REPLACE = ["\x1c", '[*]', '.*', '[?]', '.', '~'];
private static function makeDateObject(int $year, int $month, int $day, int $hour = 0, int $minute = 0, int $second = 0): DateTime
{
$baseDate = new DateTime();
@ -853,8 +847,7 @@ class AutoFilter
$ruleValue = $rule->getValue();
if (!is_array($ruleValue) && !is_numeric($ruleValue)) {
// Convert to a regexp allowing for regexp reserved characters, wildcards and escaped wildcards
$ruleValue = preg_quote("$ruleValue");
$ruleValue = str_replace(self::FROM_REPLACE, self::TO_REPLACE, $ruleValue);
$ruleValue = WildcardMatch::wildcard($ruleValue);
if (trim($ruleValue) == '') {
$customRuleForBlanks = true;
$ruleValue = trim($ruleValue);

View File

@ -346,4 +346,10 @@ return [
['Obtuse', 'Amuse', 'Obverse', 'Inverse', 'Assurance', 'Amplitude', 'Adverse', 'Apartment'],
0,
],
[
3, // Expected
'*~~*', // contains a tilde
['aAAAAA', 'a123456*c', 'abc~xyz', 'alembic'],
0,
],
];