From a7ca9c2e1a8dfc5c4d4b98b0935fec8880d40774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20PENHO=C3=8BT?= Date: Mon, 21 Feb 2022 15:51:53 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20possibilit=C3=A9=20ou=20non=20de=20rend?= =?UTF-8?q?re=20le=20moteur=20de=20recherche=20sensible=20=C3=A0=20la=20ca?= =?UTF-8?q?sse,=20aux=20accents=20ou=20encore=20=C3=A0=20ignorer=20ou=20pa?= =?UTF-8?q?s=20certains=20caract=C3=A8res=20sp=C3=A9ciaux.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/SearchEngine.ts | 38 ++++++++++++++++++++++++--- src/interfaces.ts | 7 +++++ tests/searchEngineSpec.ts | 54 ++++++++++++++++++++++++++++++++++----- 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 86d75dc..23fcc0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freedatas2html", - "version": "1.3.2", + "version": "1.4.0", "description": "Conversion and display of data in different formats (CSV, JSON, HTML) with the possibility of filtering, classifying and paginating the results.", "main": "index.js", "scripts": { diff --git a/src/SearchEngine.ts b/src/SearchEngine.ts index c84260a..4eabf66 100644 --- a/src/SearchEngine.ts +++ b/src/SearchEngine.ts @@ -1,5 +1,5 @@ const errors=require("./errors.js"); -import { DOMElement, Filters } from "./interfaces"; +import { DOMElement, Filters, SearchModeSettings } from "./interfaces"; import { FreeDatas2HTML } from "./FreeDatas2HTML"; export class SearchEngine implements Filters @@ -13,6 +13,13 @@ export class SearchEngine implements Filters public placeholder: string=""; public automaticSearch: boolean=false; private _inputValue: string=""; + public searchMode:SearchModeSettings= // par défaut, recherche lâche, mais peut devenir stricte en passant tout à false + { + accentOff: true, + caseOff: true, + specialCharsOff: true, + specialCharsWhiteList: "", + } // Injection de la classe principale, mais uniquement si des données ont été importées constructor(converter: FreeDatas2HTML, elt: DOMElement, fields?: number[]) @@ -110,10 +117,34 @@ export class SearchEngine implements Filters mySearch._converter.refreshView(); }); } + + // Pré-traitement des chaînes de caractères à comparer, suivant le mode de recherche + private searchPreProcessing(searchElement: string) : string + { + let finalString=searchElement; + if(this.searchMode.accentOff) // caractères accentués remplacés (exemple : "é" -> "e") + finalString=finalString.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); // cf. https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript + if(this.searchMode.caseOff) + finalString=finalString.toLowerCase(); + if(this.searchMode.specialCharsOff) + { + // Suppression de tous les caractères "spéciaux", c'est-à-dire n'étant ni une lettre, ni un chiffre + // ! Doit être exécuté après "accentOff", sans quoi les caractères accentués seront supprimés avant d'être remplacés + const validChars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"+this.searchMode.specialCharsWhiteList; + let validString=""; + for(const letter of finalString) + { + if(validChars.indexOf(letter) !== -1) + validString+=letter; + } + finalString=validString; + } + return finalString; + } public dataIsOk(data: {[index: string]:string}) : boolean { - const realSearch=this._inputValue.trim().toLowerCase(); + const realSearch=this.searchPreProcessing(this._inputValue.trim()); // Pas de valeur sélectionnée = pas de filtre sur ce champ if(realSearch.length === 0) return true; @@ -122,8 +153,7 @@ export class SearchEngine implements Filters { if(this._fields2Search.indexOf(field) !== -1) { - // Attention, recherche insensible à la casse, mais aux accents, etc. - if(data[field].toLowerCase().indexOf(realSearch) !== -1) + if(this.searchPreProcessing(data[field]).indexOf(realSearch) !== -1) return true; } } diff --git a/src/interfaces.ts b/src/interfaces.ts index 112ce4c..eca7dfd 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -81,6 +81,13 @@ export interface RemoteSources extends RemoteSourceSettings { getFetchSettings() : {}; } +export interface SearchModeSettings +{ + accentOff: boolean; + caseOff: boolean; + specialCharsOff: boolean; + specialCharsWhiteList: string; +} export interface Selectors extends Filters { datasFieldNb: number; diff --git a/tests/searchEngineSpec.ts b/tests/searchEngineSpec.ts index a7d2eb8..0949f44 100644 --- a/tests/searchEngineSpec.ts +++ b/tests/searchEngineSpec.ts @@ -239,9 +239,9 @@ describe("Test du moteur de recherche.", () => it("Doit retourner false, si une donnée testée ne correspond pas à la valeur cherchée.", async () => { - searchInput.value="Halogène"; + searchInput.value="Hallogène"; searchInput.dispatchEvent(new Event("input")); - expect(mySearch.dataIsOk({ "Famille": "Halogene" })).toBeFalse();// sensible aux accents + expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeFalse(); }); it("Doit retourner true, si la valeur recherchée est trouvée dans la donnée recherchée, sans prendre en compte la casse, ni les espaces entourant la saisie.", () => @@ -254,15 +254,57 @@ describe("Test du moteur de recherche.", () => searchInput.value="gène"; searchInput.dispatchEvent(new Event("input")); expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeTrue(); - // Insensible à casse : - searchInput.value="halo"; - searchInput.dispatchEvent(new Event("input")); - expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeTrue(); // Espace entourant la saisie ignorés : searchInput.value=" halo "; searchInput.dispatchEvent(new Event("input")); expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeTrue(); + // Par défaut, la recherche doit être tolérante à la casse, à la présence ou non d'accent et ignorer les caractères n'étant ni des lettres, ni des chiffres + searchInput.value="Halogene"; + searchInput.dispatchEvent(new Event("input")); + expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeTrue(); + searchInput.value="halogène"; + searchInput.dispatchEvent(new Event("input")); + expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeTrue(); + searchInput.value="#Halogène"; + searchInput.dispatchEvent(new Event("input")); + expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeTrue(); }); + + it("Si demandé doit traiter les données avant de les comparer de manière à prendre en compte les accents, majuscules ou caractères spéciaux.", () => + { + // Sensible à casse : + mySearch.searchMode.caseOff=false; + searchInput.value="halogène"; + searchInput.dispatchEvent(new Event("input")); + expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeFalse(); + searchInput.value="Halogène"; + searchInput.dispatchEvent(new Event("input")); + expect(mySearch.dataIsOk({ "Famille": "halogène" })).toBeFalse(); + // Sensible aux accents : + mySearch.searchMode.accentOff=false; + searchInput.value="Halogene"; + searchInput.dispatchEvent(new Event("input")); + expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeFalse(); + searchInput.value="Halogène"; + searchInput.dispatchEvent(new Event("input")); + expect(mySearch.dataIsOk({ "Famille": "Halogene" })).toBeFalse(); + // Prise en compte des caractères spéciaux : + mySearch.searchMode.specialCharsOff=false; + searchInput.value="Halogène^"; + searchInput.dispatchEvent(new Event("input")); + expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeFalse(); + searchInput.value="Halogène"; + searchInput.dispatchEvent(new Event("input")); + expect(mySearch.dataIsOk({ "Famille": "Ha+logène" })).toBeFalse(); + // Ignore les caractères spéciaux, sauf ceux en liste blanche : + mySearch.searchMode.specialCharsOff=true; + mySearch.searchMode.specialCharsWhiteList="^+"; + expect(mySearch.dataIsOk({ "Famille": "Ha+logène" })).toBeFalse(); + searchInput.value="Halogène^"; + searchInput.dispatchEvent(new Event("input")); + expect(mySearch.dataIsOk({ "Famille": "Halogène" })).toBeFalse(); + }); + }); });