IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Recherche de chaîne avec une expression régulière complexe


Sujet :

Python

  1. #1
    Membre régulier

    Profil pro
    Inscrit en
    Février 2003
    Messages
    95
    Détails du profil
    Informations personnelles :
    Âge : 44
    Localisation : Etats-Unis

    Informations forums :
    Inscription : Février 2003
    Messages : 95
    Points : 85
    Points
    85
    Par défaut Recherche de chaîne avec une expression régulière complexe
    Bonjour,

    J'ai une variable qui contient une chaine de la forme :
    (0 à n caractères quelconques)
    <<
    (0 à n caractères d'espacement espace, \t, \n ...)
    /Count (un nombre de n digits)
    (0 à n caractères d'espacement espace, \t, \n ...)
    /Type
    (0 à n caractères d'espacement espace, \t, \n ...)
    /Pages
    (0 à n caractères quelconques)

    par exemple

    ...
    endobj
    781 0 obj
    <</Count 35/Type/Pages/Kids[782 0 R 783 0 R 784 0 R 785 0 R 786 0 R 787 0 R]>>
    endobj
    782 0 obj
    ...
    ou bien de la forme :
    (0 à n caractères quelconques)
    <<
    (0 à n caractères d'espacement espace, \t, \n ...)
    /Type
    (0 à n caractères d'espacement espace, \t, \n ...)
    /Pages
    (0 à n caractères quelconques)
    /Count (un nombre de n digits)
    >>
    (0 à n caractères quelconques)

    par exemple

    ...
    endobj
    3 0 obj
    << /Type /Pages /Kids [
    6 0 R
    ...
    642 0 R
    ] /Count 35
    >>
    endobj
    1 0 obj
    ...
    Je veux récupérer la valeur suivant le nom /Count (dans les exemples 35).

    J'ai écrit la reg exp suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    reg_exp = re.compile(r"<<\s*/Count.*?/Type\s*/Pages|<<\s*/Type\s*/Pages.*?/Count.*?>>", re.MULTILINE|re.DOTALL)
    puis le code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    lst = reg_exp.findall(data)
    nb_pg = lst[0][lst[0].find("/Count") + 6 :]
    if "/" in nb_pg :
        nb_pg = nb_pg[: nb_pg.find("/")]
    if "\n" in nb_pg :
        nb_pg = nb_pg[: nb_pg.find("\n")]
    nb_pg = int(nb_pg.strip())
    Cela fonctionne mais je me demande s'il n'est pas possible de condenser tout ça en une expression régulière plus complexe qui retournerait directement la valeur voulue.

    Merci à ceux qui se pencheront sur le sujet.
    Mathieu

  2. #2
    Membre éprouvé

    Profil pro
    Inscrit en
    Août 2004
    Messages
    723
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2004
    Messages : 723
    Points : 923
    Points
    923
    Par défaut
    Tu peux faire ça avec des parenthèses capturantes, mets simplement entre parenthèses ce que tu veux récupérer :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    reg_exp = re.compile(r"<<\s*/Count(.*?)/Type\s*/Pages|<<\s*/Type\s*/Pages.*?/Count(.*?)>>", re.MULTILINE|re.DOTALL)
    findall te renverra une liste de tuples, un des deux éléments du tuple sera une chaîne vide (correspondant au format non utilisé), l'autre sera ce que tu cherches.

  3. #3
    Membre extrêmement actif
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Points : 1 658
    Points
    1 658
    Par défaut
    Non, pas une regex plus complexe.
    Mais une regex plus simple, oui



    La grosse insuffisance de ta regex, c’est de ne pas utiliser le groupage des caractères cherchés. Le mot ’groupe’ induit peut être en erreur. En fait, définir des groupes dans une RE, c’est définir des goupes de caractères matchés QUI POURRONT ÊTRE RÉGURGITÉS PAR LES FONCTIONS group() ou groups()

    Comme il n’y a pas de groupe dans ta RE, tu récupères une chaîne matchante correspondant à la totalité de la RE, ce qui t’oblige au bazar du traitement de chaîne.

    Définir un groupe va réduire ce que tu obtiens à...exactement ce que tu veux grâce à la définition d’un seul groupe (enfin deux, mais c'est le même répété).





    D’après ton code ( le lst[0] ), je comprends qu’il n’y a qu’un match à trouver dans data. C’est donc dommage de faire travailler la grosse machinerie de findall() puis de devoir extraire le premier et seul élément de la liste produite; search() suffit.

    Pour récupérer la valeur d’un groupe matchant, on va donc écrire

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    nb_pg = reg_exp.search(data).group(1)
    Le 1 dans group(1) est indispensable pour que group() renvoie la valeur du groupe trouvé, même si ce groupe est unique dans la chaîne matchante parce qu’il n’y a qu’un groupe défini dans la RE.
    Car group() qui est group(0) renvoie une chaîne matchante dans son entier, quel que soit le nombre de groupes définis dans la regex.



    Il faut lire reg_exp.search(data).group(1) de la façon suivante:

    La fonction group() interroge le Match Object reg_exp.search(data) qui contient tout un tas de renseignements sur le match, pour en obtenir la valeur du group(1).

    Le Match Object reg_exp.search(data) résulte lui-même de l’application de la fonction search() au Regex Object reg_exp.


    S’il y a plusieurs matches envisageables, findall() pourra être utilisée ou aussi finditer() qui itère à la volée sur les matches sans dresser leur liste préalablement.






    Mais il faut la regex.

    Je réécris sa définition:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    reg_exp = re.compile(\
        r"<<\s*/Count.*?/Type\s*/Pages"\
        '|'\
        "<<\s*/Type\s*/Pages.*?/Count.*?>>",\
        re.MULTILINE|re.DOTALL)

    À mon avis, re.MULTILINE ne sert à rien ici parce que ce drapeau sert à faire matcher ’^’ et ’$’ respectivement après et avant une newline, et pas seulement en début de chaîne et en fin comme c’est leur rôle basique.
    Mais comme il n’y a pas ces caractères dans la RE......



    Dommage de noyer dans .*? le fait que Count est suivi d’un blanc et des caractères chiffres. Donc
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    reg_exp = re.compile(\
        r"<<\s*/Count \d*?/Type\s*/Pages"\
        '|'\
        "<<\s*/Type\s*/Pages.*?/Count \d*?>>",\
        re.DOTALL)
    Si on est sûr qu’il y a toujours des chiffres après Count, on peut remplacer * par +.



    Et on capte les chiffres par un groupage:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    reg_exp = re.compile(\
        r"<<\s*/Count (\d*?)/Type\s*/Pages"\
        '|'\
        "<<\s*/Type\s*/Pages.*?/Count (\d*?)>>",\
        re.DOTALL)
    En fait ça fait deux groupes, et ne peut pas obtenir le résultat simplement avec group(1).
    Mais comme une chaîne matchante ne pourra pas matcher à la fois avec les deux possibilités de l’alternative dans la RE, on peut se permettre d’obtenir LE résultat par une petite astuce qui nécessite d’utiliser groups(default) plutôt que group().

    groups(default) renvoie un tuple des groupes trouvés

    Attention: groups()[0] est group(1) , groups()[1] est group(2) , etc.

    Ici groups() va être un tuple de deux valeurs. Mais si l’argument default n’est pas spécifié, l’une des deux valeurs sera nécessairement None, ce qui, pour ma petite astuce, me gène. Donc on fixera cet argument à "": les valeurs des groupes non matchant seront indiqués comme étant "" et non plus None.

    D’où:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    nb_pg = "".join(reg_exp.search(data).groups(""))
    Le résultat est évidemment une chaîne qu’on peut transformer en nombre avec int() ou float()

    Ou alors
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    for m in reg_exp.finditerh(data):
        nb_pg = "".join(m.groups(""))

    Hop.
    .

  4. #4
    Membre régulier

    Profil pro
    Inscrit en
    Février 2003
    Messages
    95
    Détails du profil
    Informations personnelles :
    Âge : 44
    Localisation : Etats-Unis

    Informations forums :
    Inscription : Février 2003
    Messages : 95
    Points : 85
    Points
    85
    Par défaut
    Un grand merci à tous les deux, ça marche.
    Je suis vraiment un novice en matière de regexp donc ...

    @eyquem
    c'est toujours un plaisir de lire tes interventions
    c'est parfois un peu long mais toujours très clair
    si j'ai le temps, j'essaierai d'approfondir à partir de ça ...

    Mathieu

  5. #5
    Membre extrêmement actif
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Points : 1 658
    Points
    1 658
    Par défaut
    Je me doutais depuis longtemps qu’il devait être possible de faire ce qui suit:


    Chaine ch1 correspondant au premier cas:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import re
     
    ch1 = 'endobj\t\r\n781 0\t\f\t\tobj'\
          '<<\n\t \t'\
          '/Count 35\t\v\t'\
          '/Type\t\f\t'\
          '/Pages/Kids[782 0 R 783 0 R 784 0 R]'\
          '>>'\
          '\nendobj\n782 0 obj'
     
    reg = re.compile(\
        "(?:(<<\s*/Type\s*/Pages.*?)|(<<\s*))"\
        "/Count (\d*?)"\
        "(?(1)>>|(?(2)\s*/Type\s*/Pages.*?>>))",re.DOTALL)
     
    print reg.search(ch1).group(3)
    Résultat


    Chaîne ch2 correspondant au second cas:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import re
     
    ch2 = '\t\t\tendobj\n\t3 0 obj'\
          '<<\n\f\v\t'\
          '/Type\t\f\t\n\n\t\v\r\n'\
          '/Pages /Kids [ \t\n6 0 R 642 0 R/Count 67498>>'\
          'endobj\t\v\n\t1 0 obj'
     
    reg = re.compile(\
        r"(?:(<<\s*/Type\s*/Pages.*?)|(<<\s*))"\
        "/Count (\d*?)"\
        "(?(1)>>|(?(2)\s*/Type\s*/Pages.*?>>))",re.DOTALL)
     
    print reg.search(ch2).group(3)
    Résultat



    C'est la même regex dans les deux cas, évidemment.

    La valeur recherchée est donnée par un seul groupe, et non plus deux.
    (?(id/name)yes-pattern|no-pattern)
    Will try to match with yes-pattern if the group with given id or name exists, and with no-pattern if it doesn't. |no-pattern is optional and can be omitted. For example, (<)?(\w+@\w+(?:\.\w+)+)(?(1)>) is a poor email matching pattern, which will match with '<user@host.com>' as well as 'user@host.com', but not with '<user@host.com'. New in version 2.4.

    http://www.python.org/doc/2.5.2/lib/re-syntax.html



    La complexité est déplacée de l’écriture du résultat vers l’écriture de la regex.
    Ça marche aussi si on inverse dans la regex
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    reg2 = re.compile(\
        "(?:(<<\s*/Type\s*/Pages)|(<<\s*))"\
        '/Count (\d*?)'\
        '(?(2)\s*/Type\s*/Pages.*?>>|(?(1)>>))',re.DOTALL)


    Nota:
    tu t’es trompé dans ton premier cas, il fallait écrire:
    (0 à n caractères quelconques)
    <<
    (0 à n caractères d'espacement espace, \t, \n ...)
    /Count (un nombre de n digits)
    (0 à n caractères d'espacement espace, \t, \n ...)
    /Type
    (0 à n caractères d'espacement espace, \t, \n ...)
    /Pages
    (0 à n caractères quelconques)
    >>

    (0 à n caractères quelconques)
    Mais avec les exemples, c'est clair.
    .

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 3
    Dernier message: 28/07/2014, 01h48
  2. [FAQ] Comment tester une chaîne de caractères avec une expression régulière ?
    Par Baptiste Wicht dans le forum Vos Contributions VBScript
    Réponses: 1
    Dernier message: 20/11/2007, 19h43
  3. [JMeter] Extraction avec une expression régulière
    Par LittleBean dans le forum Tests et Performance
    Réponses: 0
    Dernier message: 04/04/2007, 17h39
  4. Problème avec une expression régulière
    Par Darkroro dans le forum Langage
    Réponses: 7
    Dernier message: 09/10/2006, 12h13
  5. [Regex] Vérifier qu'une chaîne respecte une expression régulière
    Par PeteMitchell dans le forum Collection et Stream
    Réponses: 7
    Dernier message: 13/05/2004, 14h22

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo