Recherche dans une NSString et caractères spéciaux

colas_colas_ Membre
septembre 2013 modifié dans Objective-C, Swift, C, C++ #1

Bonjour,


 


je fais une recherche dans une string.


Pour l'instant mon code est :



NSStringCompareOptions options = NSCaseInsensitiveSearch |
NSDiacriticInsensitiveSearch |
NSWidthInsensitiveSearch ;
NSString * name = @Très facile ? ;
NSRange range = NSMakeRange(0, name.length) ;

NSRange result = [name rangeOfString:stringToSearch
options:options
range:range
locale:[[NSLocale alloc] initWithLocaleIdentifier:@fr]]

return result.location != NSNotFound ;

Le problème est que si je recherche "facilé", il me retourne YES.


 


Je voudrais qu'il me retourne YES si je recherche "Tres" mais NO si je recherche "facilé".


 


Avez-vous déjà  essayé de faire cela ?


 


Merci !


Réponses

  • j'ai testé par curiosité, cela fonctionne... je te conseille de faire afficher la variable que tu testes dans un lot pour voir si est à  la bonne valeur.


  • FKDEVFKDEV Membre
    septembre 2013 modifié #3
    En gros tu veux qu'une absence d'accent matche mais pas un accent en trop.

    Je ne sais pas si c'est possible en une seule étape.


    Une solution en deux étapes consisteraient à  garder le test que tu fais deja et à  ajouter un test avec une expression régulière.

    En prenant le texte trouvé et en remplaçant toutes les lettres avec diacritic par un OU entre la lettre avec diacritic et son équivalent sans diacritic :


    Par exemple si tu as trouvé Très avec Tres, tu vérifies si Tr[èe]s matche Tres.

    Du coup facile ne matchera pas facilé.

    ou compliqu[ée] ne matchera pas complà®que mais matchera complique
  • Après quelques essais, il s'avère que transformer Très en Tr[èe]s n'est pas si simple.

    Quelques indices :

    -utiliser [[NSCharacterSet decomposableCharacterSet] characterIsMember]] pour trouver les caractères "spéciaux".


    -utiliser [NSString rangeOfComposedCharacterSequenceAtIndex] pour extraire le caractère décomposable du NSString.


    -utiliser CFStringTransform pour transformer 'è' en 'e'.

  • @denis : oui tu as raison. Avec le nouveau code, ça ne devrait pas marcher !


  • @FKDEV : merci pour ces pistes !


  • AliGatorAliGator Membre, Modérateur
    septembre 2013 modifié #7
    Il faut supprimer tes diacritiques dans la string dans laquelle tu recherches (et faire une recherche sans l'option "NSDiacriticInsensitiveSearch" du coup).

    En gros, tu convertis ton "Très facile" en "Tres facile", puis tu cherches dedans le texte "Tres" (qu'il va trouver) ou "facilé" (qu'il ne va pas trouver).

    Pour enlever les accents de ta chaà®ne, tu as la méthode "stringByFoldingWithOptions:locale:" de NSString, à  laquelle il suffit de passer l'option "NSDiacriticInsensitiveSearch", et basta.


  • Il faut supprimer tes diacritiques dans la string dans laquelle tu recherches (et faire une recherche sans l'option "NSDiacriticInsensitiveSearch" du coup).




    Putain, j'viens d'apprends que toutes ces p'tites merdes avaient un nom : diacritique. J'ai été obligé de googler/wikipédier ça :o

    Intéressant en tout cas.

  • @Ali : je pense que ta solution bugue : si je recherche "Très", il ne va pas trouver !


  • AliGatorAliGator Membre, Modérateur
    Anéfé ;)


    Il faut d'abord chercher le texte dans la chaà®ne d'origine avec accents et ne chercher dans la chaà®ne où on a retiré les accents que si on a pas trouvé avant
  • Ali, je crois que ça ne fonctionnera pas avec un mot où on aurait oublié un accent comme "apprecié" au lieu de "apprécié".


  • colas_colas_ Membre
    septembre 2013 modifié #12

    Voici une méthode qui marche.


    C'est une catégorie sur NSString.


     


    @Ali et FKDEV : merci pour votre aide !



    - (NSRange)rangeOfStringForFrench:(NSString *)aString
    onlyFromBeginning:(BOOL)onlyFromBeginning
    onlyFromEnd:(BOOL)onlyFromEnd
    {
    NSStringCompareOptions options = NSCaseInsensitiveSearch ;

    if (onlyFromBeginning)
    {
    options = options | NSAnchoredSearch ;
    }
    else if (onlyFromEnd)
    {
    options = options | NSAnchoredSearch | NSBackwardsSearch ;
    }

    NSRange range = NSMakeRange(0, self.length) ;

    NSRange firstResult = [self rangeOfString:aString
    options:options
    range:range] ;

    NSRange notFound = {NSNotFound, 0} ;

    if (firstResult.location != NSNotFound)
    {
    return firstResult ;
    }
    else
    {
    NSString * selfWithoutAccents = [self stringByFoldingWithOptions:NSDiacriticInsensitiveSearch
    locale:[[NSLocale alloc] initWithLocaleIdentifier:@fr]] ;

    NSRange secondResult = [selfWithoutAccents rangeOfString:aString
    options:options
    range:range] ;

    if (secondResult.location != NSNotFound)
    {
    return secondResult ;
    }
    else
    {
    return notFound ;
    }
    }
    }
  • AliGatorAliGator Membre, Modérateur
    septembre 2013 modifié #13

    Ali, je crois que ça ne fonctionnera pas avec un mot où on aurait oublié un accent comme "apprecié" au lieu de "apprécié".

    Bah non évidemment, mais de ce que j'avais compris c'est ce qui était voulu, non ?

    En fait la règle n'est pas claire, où est-ce qu'il veut bien que ça trouve et dans quel cas faut-il que ça ne trouve pas ?

    Parce que si tu regardes l'exemple "facilé" / facile on pourrait penser que ça rentre dans le même cas que "apprécié" / "apprecié" non ?
  • colas_colas_ Membre
    septembre 2013 modifié #14

    @FKDEV : oui, tu as raison !


    @Ali : la règle c'est que si j'oublie un accent ça matche, si je mets un accent en trop, ça ne matche pas.


     


    Du coup, peut-être qu'il faut tester caractère par caractère...


  • colas_colas_ Membre
    septembre 2013 modifié #15

    Je m'en suis sorti avec une méthode astucieuse !


    En effet, si on compare les NSString, alors ≥ @e.


     


    Je fais donc comme suit : je teste d'abord sans les accents.


    Je récupère la sous-string qui matche.


    Puis, je compare (au sens ≤) string cherchée avec celle trouvée.



    - (NSRange)rangeOfStringForFrench:(NSString *)aString
    onlyFromBeginning:(BOOL)onlyFromBeginning
    onlyFromEnd:(BOOL)onlyFromEnd
    {
    NSRange notFound = {NSNotFound, 0} ;

    if (!aString)
    {
    return notFound ;
    }

    NSStringCompareOptions options = NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch ;

    if (onlyFromBeginning)
    {
    options = options | NSAnchoredSearch ;
    }
    else if (onlyFromEnd)
    {
    options = options | NSAnchoredSearch | NSBackwardsSearch ;
    }

    NSRange range = NSMakeRange(0, self.length) ;

    NSRange firstResult = [self rangeOfString:aString
    options:options
    range:range
    locale:[[NSLocale alloc] initWithLocaleIdentifier:@fr]] ;


    if (firstResult.location == NSNotFound)
    {
    return notFound ;
    }
    else
    {
    NSComparisonResult resultCompare = [aString compare:[self substringWithRange:firstResult]
    options:NSCaseInsensitiveSearch] ;

    if (resultCompare == NSOrderedAscending || resultCompare == NSOrderedSame)
    {
    return firstResult ;
    }
    else
    {
    return notFound ;
    }
    }
    }


    PS : il va encore rester quelques bugs quand même... 


    Par exemple, "Prè" matche "Pré"


  • AliGatorAliGator Membre, Modérateur
    septembre 2013 modifié #16

    @FKDEV : oui, tu as raison !
    @Ali : la règle c'est que si j'oublie un accent ça matche, si je mets un accent en trop, ça ne matche pas.

    Et si tu mets le mauvais accent ? Genre "événement" vs "évènement" ?
    Et si tu mets du multi-diacritiques, un peu dans tous les sens, genre un O avec accent aigu + un accent rond (à  la suédoise) et que tu le compares avec un 0 avec cédille et barré (à  la norvégienne) ?
  • Ali, ma solution marche dans tout ces cas là .


     


    Mais je trouve quand meme que la solution trouvée par colas est maligne.


  • J'ai finalement implémenté la solution de FKDEV :


     


    Les méthodes auxiliaires :


    1) une méthode qui décompose une string en array de strings de taille 1


    2) une méthode qui crée l'expression régulière



    - (NSRange)rangeOfStringForFrench:(NSString *)aString
    onlyFromBeginning:(BOOL)onlyFromBeginning
    onlyFromEnd:(BOOL)onlyFromEnd
    {
    NSRange notFound = {NSNotFound, 0} ;

    if (!aString)
    {
    return notFound ;
    }

    NSStringCompareOptions options = NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch ;

    if (onlyFromBeginning)
    {
    options = options | NSAnchoredSearch ;
    }
    else if (onlyFromEnd)
    {
    options = options | NSAnchoredSearch | NSBackwardsSearch ;
    }

    NSRange range = NSMakeRange(0, self.length) ;

    NSRange firstResult = [self rangeOfString:aString
    options:options
    range:range
    locale:[[NSLocale alloc] initWithLocaleIdentifier:@fr]] ;


    if (firstResult.location == NSNotFound)
    {
    return notFound ;
    }
    else
    {
    NSString * matchingString = [self substringWithRange:firstResult] ;
    NSRegularExpression * regExp = [self createRegularExpressionFor:matchingString] ;


    BOOL isMatching = [[regExp matchesInString:aString
    options:0
    range: NSMakeRange(0, aString.length) ] count] != 0 ;

    if (isMatching)
    {
    return firstResult ;
    }
    else
    {
    return notFound ;
    }
    }
    }




    - (NSRegularExpression *)createRegularExpressionFor:(NSString *)aString
    {
    NSMutableString * stringRegExp = [[NSMutableString alloc] init] ;

    NSString * aStringBis = [aString stringByFoldingWithOptions:NSDiacriticInsensitiveSearch
    locale:[[NSLocale alloc] initWithLocaleIdentifier:@fr]] ;

    NSArray * characters = [self arrayOfCharactersOf:aString] ;
    NSArray * charactersBis = [self arrayOfCharactersOf:aStringBis] ;


    for (NSUInteger index = 0 ; index < [aString length] ; index++)
    {
    NSString * character = characters[index] ;
    NSString * characterBis = charactersBis[index] ;

    if ([character isEqualToString:characterBis])
    {
    [stringRegExp appendString:character] ;
    }
    else
    {
    [stringRegExp appendString:[NSString stringWithFormat:@[;%@%@]", characterBis, character]];
    }
    }

    NSError * error = NULL ;

    return [NSRegularExpression regularExpressionWithPattern:stringRegExp
    options:NSRegularExpressionCaseInsensitive
    error:&error];

    }




    - (NSArray *)arrayOfCharactersOf:(NSString *)aString
    {
    NSMutableArray * result = [[NSMutableArray alloc] initWithCapacity:[aString length]];

    [aString enumerateSubstringsInRange:NSMakeRange(0, aString.length)
    options:NSStringEnumerationByComposedCharacterSequences
    usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop)
    {
    [result addObject:substring];
    }];

    return result ;
    }
  • C'est pas trop lent ?


  • Sur mon mac book pro, pas de problème, ça tourne !


Connectez-vous ou Inscrivez-vous pour répondre.