Complete selection value in domain parser using contains matcher

Closes #14886
This commit is contained in:
Cédric Krier
2026-06-10 19:10:53 +02:00
parent 1e053eb4c6
commit 75ea4f7587
5 changed files with 70 additions and 20 deletions
+14 -11
View File
@@ -1588,6 +1588,15 @@
}
return results;
},
contains_without_diacritics: function(main, sub) {
main = main.normalize('NFD').toLowerCase();
sub = sub.normalize('NFD').toLowerCase();
main = main.replace(/[\u0300-\u036f]/g, '');
sub = sub.replace(/[\u0300-\u036f]/g, '');
return main.includes(sub);
},
complete_value: function(field, value) {
var complete_boolean = function() {
if ((value === null) || (value === undefined)) {
@@ -1599,18 +1608,15 @@
}
};
var complete_selection = function() {
var complete_selection = () => {
var results = [];
var test_value = value !== null ? value : '';
if (value instanceof Array) {
test_value = value[value.length - 1] || '';
}
test_value = test_value.replace(/^%*|%*$/g, '');
var i, len, svalue, test;
for (i=0, len=field.selection.length; i<len; i++) {
svalue = field.selection[i][0];
test = field.selection[i][1].toLowerCase();
if (test.startsWith(test_value.toLowerCase())) {
for(let [svalue, test] of field.selection) {
if (this.contains_without_diacritics(test, test_value)) {
if (value instanceof Array) {
results.push(value.slice(0, -1).concat([svalue]));
} else {
@@ -1628,11 +1634,8 @@
test_value = value[value.length - 1];
}
test_value = test_value.replace(/^%*|%*$/g, '');
var i, len, svalue, test;
for (i=0, len=field.selection.length; i<len; i++) {
svalue = field.selection[i][0];
test = field.selection[i][1].toLowerCase();
if (test.startsWith(test_value.toLowerCase())) {
for (let [svalue, test] of field.selection) {
if (this.contains_without_diacritics(test, test_value)) {
if (value instanceof Array) {
results.push(value.slice(0, -1).concat([svalue]));
} else {
+1
View File
@@ -3,6 +3,7 @@
this repository contains the full copyright notices and license terms. -->
<html>
<head>
<meta charset="UTF-8">
<title>Sao Test Suite</title>
<script type="text/javascript" src="../node_modules/jquery/dist/jquery.js"></script>
<script type="text/javascript" src="../node_modules/bootstrap/dist/js/bootstrap.js"></script>
+21 -2
View File
@@ -2427,6 +2427,25 @@
});
});
QUnit.test('DomainParser.contains_without_diacritics', function() {
let parser = new Sao.common.DomainParser();
for (let [main, sub, expected] of [
["Café au lait", "cafe", true],
["Café au lait", "café", true],
["Café au lait", "lait", true],
["Café au lait", "the", false],
["Héllò Wörld", "hello", true],
["Héllò Wörld", "world", true],
["Héllò Wörld", "hallo", false],
]) {
QUnit.assert.strictEqual(
parser.contains_without_diacritics(main, sub), expected,
'contains_without_diacritics('
+ JSON.stringify(main) + ',' + JSON.stringify(sub) + ')');
}
});
QUnit.test('DomainParser.complete_value', function () {
var parser = new Sao.common.DomainParser();
var field;
@@ -2456,7 +2475,7 @@
],
};
[
['m', ['male']],
['f', ['female']],
['test', []],
['', ['male', 'female']],
[null, ['male', 'female']],
@@ -2473,7 +2492,7 @@
],
};
[
['m', ['male']],
['f', ['female']],
['test', []],
['', ['male', 'female', '']],
[null, ['male', 'female', '']],
+14 -3
View File
@@ -6,6 +6,7 @@ import gettext
import io
import locale
import math
import unicodedata
from collections import OrderedDict
from decimal import Decimal
from shlex import shlex
@@ -426,6 +427,16 @@ def format_value(field, value, target=None, context=None, _quote_empty=False):
lambda: value if value is not None else '')(), empty=_quote_empty)
def contains_without_diacritics(main, sub):
main = unicodedata.normalize('NFD', main.lower())
sub = unicodedata.normalize('NFD', sub.lower())
main = ''.join(c for c in main if not unicodedata.combining(c))
sub = ''.join(c for c in sub if not unicodedata.combining(c))
return sub in main
def complete_value(field, value):
"Complete value for field"
@@ -444,7 +455,7 @@ def complete_value(field, value):
test_value = value[-1] or ''
test_value = test_value.strip('%')
for svalue, test in field['selection']:
if test.lower().startswith(test_value.lower()):
if contains_without_diacritics(test, test_value):
if isinstance(value, list):
yield value[:-1] + [svalue]
else:
@@ -454,9 +465,9 @@ def complete_value(field, value):
test_value = value if value is not None else ''
if isinstance(value, list):
test_value = value[-1]
test_value = test_value.strip('%')
test_value = test_value.strip('%').lower()
for svalue, test in field['selection']:
if test.lower().startswith(test_value.lower()):
if contains_without_diacritics(test, test_value):
if isinstance(value, list):
yield value[:-1] + [svalue]
else:
@@ -6,8 +6,9 @@ from unittest import TestCase
from tryton.common import untimezoned_date
from tryton.common.domain_parser import (
DomainParser, complete_value, convert_value, format_value, group_operator,
likify, operatorize, parenthesize, quote, rlist, split_target_value, udlex)
DomainParser, complete_value, contains_without_diacritics, convert_value,
format_value, group_operator, likify, operatorize, parenthesize, quote,
rlist, split_target_value, udlex)
class DomainParserTestCase(TestCase):
@@ -433,6 +434,21 @@ class DomainParserTestCase(TestCase):
format_value(field, value), result,
msg="format_value(%r, %r)" % (field, value))
def test_contains_without_diacritics(self):
"Test contains without diacritics"
for main, sub, expected in [
("Café au lait", "cafe", True),
("Café au lait", "café", True),
("Café au lait", "lait", True),
("Café au lait", "the", False),
("Héllò Wörld", "hello", True),
("Héllò Wörld", "world", True),
("Héllò Wörld", "hallo", False),
]:
with self.subTest(main=main, sub=sub):
self.assertEqual(
contains_without_diacritics(main, sub), expected)
def test_complete_boolean(self):
"Test complete boolean"
field = {
@@ -457,7 +473,7 @@ class DomainParserTestCase(TestCase):
],
}
for value, result in (
('m', ['male']),
('f', ['female']),
('test', []),
('', ['male', 'female']),
(None, ['male', 'female']),
@@ -472,7 +488,7 @@ class DomainParserTestCase(TestCase):
field_with_empty['selection'] = (field_with_empty['selection']
+ [('', '')])
for value, result in (
('m', ['male']),
('f', ['female']),
('test', []),
('', ['male', 'female', '']),
(None, ['male', 'female', '']),