From 93b9b29b3bf4ff627f965fb632a00a3d1dab0e69 Mon Sep 17 00:00:00 2001 From: Wieland Schopohl Date: Wed, 15 Apr 2026 02:34:23 +0200 Subject: [PATCH] Support comma as decimal separator in SI value parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part names using European decimal notation (e.g. "4,7 kΩ", "2,2uF") were parsed incorrectly because the regex only recognized dots. Now commas are normalized to dots before parsing, matching the existing pattern used elsewhere in the codebase (PartNormalizer, price providers). --- src/Doctrine/Functions/SiValueSort.php | 11 ++++++++++- tests/Doctrine/Functions/SiValueSortTest.php | 11 ++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Doctrine/Functions/SiValueSort.php b/src/Doctrine/Functions/SiValueSort.php index 3d0a7cc8..1bba1b9f 100644 --- a/src/Doctrine/Functions/SiValueSort.php +++ b/src/Doctrine/Functions/SiValueSort.php @@ -82,7 +82,10 @@ class SiValueSort extends FunctionNode assert($this->field !== null, 'Field is not set'); $platform = $sqlWalker->getConnection()->getDatabasePlatform(); - $fieldSql = $this->field->dispatch($sqlWalker); + $rawField = $this->field->dispatch($sqlWalker); + + // Normalize comma decimal separator to dot for SQL platforms (European locale support) + $fieldSql = "REPLACE({$rawField}, ',', '.')"; if ($platform instanceof PostgreSQLPlatform) { return $this->getPostgreSQLSql($fieldSql); @@ -92,6 +95,9 @@ class SiValueSort extends FunctionNode return $this->getMySQLSql($fieldSql); } + // SQLite: comma normalization is handled in the PHP callback + $fieldSql = $rawField; + if ($platform instanceof SQLitePlatform) { return "SI_VALUE({$fieldSql})"; } @@ -168,6 +174,9 @@ class SiValueSort extends FunctionNode return null; } + // Normalize comma decimal separator to dot (European locale support) + $value = str_replace(',', '.', $value); + // Match a number at the very start (allowing leading whitespace), optionally followed by an SI prefix if (!preg_match('/^\s*(\d+\.?\d*)\s*([pnuµmkKMGT])?/u', $value, $matches)) { return null; diff --git a/tests/Doctrine/Functions/SiValueSortTest.php b/tests/Doctrine/Functions/SiValueSortTest.php index 4c13ff69..dbdd9d28 100644 --- a/tests/Doctrine/Functions/SiValueSortTest.php +++ b/tests/Doctrine/Functions/SiValueSortTest.php @@ -37,7 +37,7 @@ final class SiValueSortTest extends AbstractDoctrineFunctionTestCase $sql = $function->getSql($this->createSqlWalker(new PostgreSQLPlatform())); $this->assertStringContainsString('CASE', $sql); - $this->assertStringContainsString('substring(part_name', $sql); + $this->assertStringContainsString("REPLACE(part_name, ',', '.')", $sql); $this->assertStringContainsString('1e-12', $sql); $this->assertStringContainsString('1e-9', $sql); $this->assertStringContainsString('1e-6', $sql); @@ -56,7 +56,7 @@ final class SiValueSortTest extends AbstractDoctrineFunctionTestCase $sql = $function->getSql($this->createSqlWalker(new MySQLPlatform())); $this->assertStringContainsString('CASE', $sql); - $this->assertStringContainsString('REGEXP_SUBSTR(part_name', $sql); + $this->assertStringContainsString("REPLACE(part_name, ',', '.')", $sql); $this->assertStringContainsString('1e-12', $sql); $this->assertStringContainsString('1e6', $sql); } @@ -106,11 +106,16 @@ final class SiValueSortTest extends AbstractDoctrineFunctionTestCase yield 'plain_integer' => ['100', 100.0]; yield 'plain_decimal' => ['4.7', 4.7]; - // Decimal values with prefix + // Decimal values with prefix (dot separator) yield 'decimal_nano' => ['4.7nF', 4.7e-9]; yield 'decimal_micro' => ['0.1uF', 0.1e-6]; yield 'decimal_kilo' => ['2.2k', 2.2e3]; + // Comma decimal separator (European locale) + yield 'comma_kilo' => ['4,7k', 4.7e3]; + yield 'comma_micro' => ['2,2uF', 2.2e-6]; + yield 'comma_kilo_space' => ['1,2 kΩ', 1.2e3]; + // Number NOT at the start — should return NULL yield 'prefixed_name' => ['CAP-100nF', null]; yield 'name_with_number' => ['R 4.7k 1%', null];