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 :

Optimisation regexp sur contenu html


Sujet :

Python

  1. #1
    Membre confirmé Avatar de arnaudk
    Inscrit en
    Février 2009
    Messages
    38
    Détails du profil
    Informations forums :
    Inscription : Février 2009
    Messages : 38
    Par défaut Optimisation regexp sur contenu html
    Bonsoir,

    Les regexp c'est beau, sauf quand on s'arrache les cheveux dessus.
    Mon problème est assez simple : dans un contenu html, j'aimerais "neutraliser" tous les symboles dollars présents entre des balises pre, mais pas les autres.

    La raison est que tous les autres dollars servent à générer des formules compilées en latex, et les $ présents entre les balise pre sont des bouts de code php ou jquery dans mes différents tutos.

    Voici un exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    <p>des dollars ici $33$ et ici $$45$$ et dans un pre <pre> voila $ bla bla $ bla $ et $$ </pre> et encore dehors $86$</p>
    Le filtre latex va remplacer par exemple $33$ par une image <img .../>

    Le seul moyen que j'ai trouvé de "neutraliser" ces dollars entre balises pre, c'est de les remplacer par une autre chaine, le temps de compiler les formules latex, puis revenir en arrière.
    Donc cela donnerait un truc du style :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    <p>des dollars ici $33$ et ici $$45$$ et dans un pre <pre> voila YOUPI bla bla YOUPI bla YOUPI et YOUPIYOUPI </pre> et encore dehors $86$</p>
    puis compilation latex des images :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    <p>des dollars ici <img.../> et ici <img.../> et dans un pre <pre> voila YOUPI bla bla YOUPI bla YOUPI et YOUPIYOUPI </pre> et encore dehors <img.../></p>
    et on revient en arrière, on vire les YOUPI pour des dollars :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    <p>des dollars ici <img.../> et ici <img.../> et dans un pre <pre> voila $ bla bla $ bla $ et $$</pre> et encore dehors <img.../></p>
    Sauf que, non seulement cela fait beaucoup de scan complets, mais en plus je n'arrive pas à trouver une unique regexp pour remplacer simplement tous les $ entre pre d'un coup ( je m'en tire avec plusieurs recherches successives ).

    C'est loin d'être joli et optimal.
    Auriez-vous une idée pour simplifier cela ?

  2. #2
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Voilà une solution simple qui a l'air de fonctionner:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
     
    def sub(g):
        return g.group(0).replace('$', 'YOUPI')
     
    ch = "<p>des dollars ici $33$ et ici $$45$$ et dans un pre <pre> voila $ bla bla $ bla $ et $$ </pre> et encore dehors $86$</p>"
     
    print re.sub(r'(<pre>)([^<]*)(</pre>)', sub, ch)
     
    <p>des dollars ici $33$ et ici $$45$$ et dans un pre <pre> voila YOUPI bla bla YOUPI bla YOUPI et YOUPIYOUPI </pre> et encore dehors $86$</p>
    Le principe est simple: le motif isole chaque séquence "<pre>.....</pre>", et la fonction sub remplace tous les '$' par 'YOUPI'. On peut, bien sûr, faire le remplacement inverse plus tard.

    Il pourrait y avoir plusieurs séquences "<pre>.....</pre>", et ces séquences peuvent être coupées par une fin de ligne. Par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    ch = """xx$xx$$xx<pre>xx$xx$$xx</pre>xx$xx$$xx<pre>xx$x
    x$$xx</pre>xx$xx$$xx<pre>xx$xx$$xx</pre>xx$xx$$xx"""
     
    print re.sub(r'(<pre>)([^<]*)(</pre>)', sub, ch)
     
    xx$xx$$xx<pre>xxYOUPIxxYOUPIYOUPIxx</pre>xx$xx$$xx<pre>xxYOUPIx
    xYOUPIYOUPIxx</pre>xx$xx$$xx<pre>xxYOUPIxxYOUPIYOUPIxx</pre>xx$xx$$xx
    Pour accepter qu'il y ait plusieurs séquences possibles "<pre>.....</pre>", il ne doit pas y avoir de '<' entre "<pre>" et "</pre>". S'il n'y a qu'une seule séquence, on peut faire sauter cette contrainte en remplaçant dans le motif "([^<]*)" par "(.*)".

  3. #3
    Membre Expert

    Homme Profil pro
    Diverses et multiples
    Inscrit en
    Mai 2008
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Diverses et multiples

    Informations forums :
    Inscription : Mai 2008
    Messages : 662
    Par défaut
    Il y a moyen d’être plus restrictif sur le test, en ne s’arrêtant que sur une balise </pre>, avec un petit look ahead négatif*:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    r'(<pre>)(?:()(</pre>)|((?:.(?!</pre>))*.)(</pre>))'
    Ce qui se “traduit” par*: «*On capture la chaîne “<pre>” suivie de soit*:
    ×rien et la balise fermante </pre>, ou…
    ×zéro, un ou plusieurs caractères tant qu’ils ne sont pas suivis par la chaîne “</pre>” (sans oublier de capturer le dit dernier caractère, le petit . final…), suivis de la chaîne “</pre>”.

    À noter que la première partie de la condition est indispensable, sinon la chaîne “<pre></pre>” ne sera pas détectée…

    Le seul truc que cette regex ne sait pas gérer, ce sont les imbrications de balises <pre> –*mais ça, il n’y a que les moteurs de regex supportant la récursivité qui peuvent le faire.

  4. #4
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Merci, mont29, ça manquait effectivement pour faire sauter la contrainte!

    Mais ta 1ère solution (avant correction), plus simple, fonctionnait très bien pour détecter aussi les "<pre></pre>":

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    ch = """xx$xx$$xx<pre>xx$xx$$xx</pre>xx$xx$$xx<pre></pre>xx$xx$$xx"""
     
    print re.sub(r'(<pre>)((?:.(?!</pre>))*.?)(</pre>)', sub, ch)
     
    xx$xx$$xx<pre>xxYOUPIxxYOUPIYOUPIxx</pre>xx$xx$$xx<pre></pre>xx$xx$$xx
    Sont effectivement trouvées les 2 séquences:
    1ère séquence: <pre>xx$xx$$xx</pre>
    2ème séquence: <pre></pre>

  5. #5
    Membre Expert

    Homme Profil pro
    Diverses et multiples
    Inscrit en
    Mai 2008
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Diverses et multiples

    Informations forums :
    Inscription : Mai 2008
    Messages : 662
    Par défaut
    Oui, mais non*!

    En fait, là ça fonctionne parce que le groupe <pre></pre> est le dernier… Sinon, * et consort étant gloutons, ils “boufferont” tout jusqu’au </pre> suivant (dans “foo <pre></pre> bar <pre> blabla</pre>”, il y aura qu’un seul groupe de trouvé, “</pre> bar <pre> blabla”).

    Du coup, je me dis qu’il y a peut-être plus simple que le look ahead*: les opérateurs non-gloutons*:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    r'(<pre>)(.*?)(</pre>)'
    Pas le temps de tester, par contre…

  6. #6
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    @mont29:

    Effectivement, le motif précédent que je citais ne marchait pas dans tous les cas.

    Ton dernier motif a l'air de fonctionner:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    def sub(g):
        print u"séquence trouvée: ", g.group(0)
        return g.group(0).replace('$', 'YOUPI')
     
    ch = """<pre></pre>xx$xx$$xx<pre>xx$xx$$xx</pre>xx$xx$$xx<pre></pre>xx$xx$$xx<pre>xx$xx$$xx</pre>xx$xx$$xx<pre></pre>"""
     
    print re.sub(r'(<pre>)(.*?)(</pre>)', sub, ch)
    Ce qui affiche:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    séquence trouvée:  <pre></pre>
    séquence trouvée:  <pre>xx$xx$$xx</pre>
    séquence trouvée:  <pre></pre>
    séquence trouvée:  <pre>xx$xx$$xx</pre>
    séquence trouvée:  <pre></pre>
    <pre></pre>xx$xx$$xx<pre>xxYOUPIxxYOUPIYOUPIxx</pre>xx$xx$$xx<pre></pre>xx$xx$$xx<pre>xxYOUPIxxYOUPIYOUPIxx</pre>xx$xx$$xx<pre></pre>
    Manifestement, toutes les séquences "<pre>...</pre>" sont trouvées, mais je ne comprends pas comment chaque recherche s'arrête à </pre> avec simplement ".*?": tu peux expliquer?


    [Edit] j'ai compris: '.*' (=glouton) s'arrête au dernier "</pre>", et '.*?' (=non glouton) s'arrête au premier "</pre>" qu'il rencontre. Je ne me rappelais plus que '*?' était un élément de motif en soit. Merci!

  7. #7
    Membre confirmé Avatar de arnaudk
    Inscrit en
    Février 2009
    Messages
    38
    Détails du profil
    Informations forums :
    Inscription : Février 2009
    Messages : 38
    Par défaut
    Bonsoir à vous et merci pour ces réponses fructueuses.
    Je n'avais pas réussi à isoler les balises pre, et j'avais aussi tenté une solution empruntée à un topic purement php qui consistait à trouver les dollars suivis d'une balise </pre>, mais je coinçais sur la traduction en python.

    Je teste ce week-end et vous tiens au courant.

  8. #8
    Membre confirmé Avatar de arnaudk
    Inscrit en
    Février 2009
    Messages
    38
    Détails du profil
    Informations forums :
    Inscription : Février 2009
    Messages : 38
    Par défaut
    Ok, cela tourne, j'ai dû modifier un peu l'expression régulière, car je n'avais pas pensé que les balises avaient de temps en temps des attributs, et il faut aussi considérer les sauts de ligne :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    regexpre = re.compile(r'(<pre[^>]*>)(.*?)(</pre>)', re.DOTALL)
    Maintenant, du point de vue de la conception du programme, cela fait beaucoup de "scans" de la sortie html :

    1) Premier scan pour remplacer les $ dans les balises pre
    2) Chercher et remplacer par des images toutes les formules centrées en latex ( cela correspond à un motif du type $$FORMULE$$ )
    3) Chercher et remplacer par des images toutes les formules normales en latex ( cela correspond à un motif du type $FORMULE$ )
    4) Remettre les $ dans les balises pre.

    Soit 6 scans complets en tout.
    Existe-t-il un moyen plus rapide pour obtenir le même résultat ?

  9. #9
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Oui, tu as raison: on s'est donné beaucoup de mal à identifier les séquences <pre>...</pre> qui sont en fait les seules parties de la chaine qui n'ont pas besoin d'être modifiées!!! On a bonne mine...

    Mais ça va nous servir. Voilà ce que je te propose pour faire le traitement d'un seul coup.

    Ce qu'il faut savoir, c'est que dans la petite fonction sub qui est appelée à chaque détection de séquence, cette séquence est située dans la chaine à traiter par un indice début: g.start(0) et un indice de fin: g.end(0) (les '0' marquent le groupe). Donc, si on a conservé l'indice final de la séquence précédente, on obtient facilement la chaine qui précède la séquence trouvée, afin de la traiter.

    D'où la solution sous forme d'une classe (copieusement commentée):

    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
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    import re
     
    #############################################################################
    class Sub(object):
     
        def __init__(self, ch):
            # stockage de la chaine à traiter
            self.ch = ch 
            # initialisation de la chaine qui portera le résultat
            self.result = ""
            # initialisation de l'indice de fin de la séquence précédente
            self.i = 0
     
        def __call__(self, g):
            # identification de la chaine qui précède la séquence trouvée
            chprec = self.ch[self.i:g.start(0)]
            # traitement et stockage de cette chaine
            chprec = chprec.replace('$', 'TOTO')
            self.result += chprec + g.group(0)
            # mise à jour de l'indice de la prochaine chaine à traiter
            self.i = g.end(0)
            # retour de la séquence trouvée sans modification
            return g.group(0)
     
     
        def fin(self):
            # identification de la chaine qui suit la dernière séquence trouvée
            chsuiv = self.ch[self.i:len(self.ch)]
            # traitement et stockage de cette chaine
            chsuiv = chsuiv.replace('$', 'TOTO')
            self.result += chsuiv
     
    motif = re.compile(r'(<pre[^>]*>)(.*?)(</pre>)', re.DOTALL)
     
    #############################################################################
     
    ch = """1xx$xx$xx1<pre>xx$xx$xx</pre>2xx$xx$xx2<pre></pre>3xx$xx$xx3<pre>xx$xx$xx</pre>4xx$xx$xx4"""
     
    traitement = Sub(ch)
    motif.sub(traitement, ch)
    traitement.fin()  #  <==  ne pas oublier, sinon il manquera la fin de la chaine
     
    print traitement.result
    Ici, on a transformé tous les '$' par 'TOTO' dans toutes les chaines situées avant, entre et après les séquences '<pre>...</pre>' (mais pas à l'intérieur de ces séquences qui restent intactes!)

    Ce qui donne comme résultat:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1xxTOTOxxTOTOxx1<pre>xx$xx$xx</pre>2xxTOTOxxTOTOxx2<pre></pre>3xxTOTOxxTOTOxx3<pre>xx$xx$xx</pre>4xxTOTOxxTOTOxx4
    Il ne reste plus qu'à écrire le traitement réel en remplacement de mon: '$' => 'TOTO'.

    Ok?

  10. #10
    Membre confirmé Avatar de arnaudk
    Inscrit en
    Février 2009
    Messages
    38
    Détails du profil
    Informations forums :
    Inscription : Février 2009
    Messages : 38
    Par défaut
    Merci tyrtamos pour la réponse.
    Je ne trouverai pas le temps d'adapter le tout avant la semaine prochaine à cause de mon boulot, mais quoi qu'il en soit, je me manifeste à nouveau.

  11. #11
    Membre confirmé Avatar de arnaudk
    Inscrit en
    Février 2009
    Messages
    38
    Détails du profil
    Informations forums :
    Inscription : Février 2009
    Messages : 38
    Par défaut
    J'avais dit une semaine, mais finalement j'ai eu plus de boulot que prévu.
    La dernière solution proposée par tyrtamos fonctionne très bien toute seule, mais à partir du moment où j'essaye de l'inclure dans mon code, j'obtiens des valeurs vides. Le problème se situe au niveau de la méthode __call__ qui n'est pas appelée, et je ne sais pas pourquoi.
    D'ailleurs je n'ai pas compris à quoi correspond le paramètre g de cette méthode.

    Voici en gros une structure tentée :

    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
    17
    import re # et le reste
     
    class Sub(object):
       BLABLA
     
    def fonction_de_traitement():
       BLIBLI
     
    def filtre(ch):
       motif = re.compile(r'(<pre[^>]*>)(.*?)(</pre>)', re.DOTALL)
       traitement = Sub(ch)
       motif.sub(traitement, ch) #problème, car ici __call__ n'est pas appelée 
       traitement.fin()
     
       fonction_de_traitement()
     
       return result()

  12. #12
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    La méthode __call__ s'appelle comme ça pour des raisons purement esthétiques: cela permet de passer "traitement" comme fonction de remplacement à sub. Si tu veux l'appeler "go", par exemple, tu devras passer "traitement.go" à sub.

    sub attend soit une chaine, et c'est alors une chaine de remplacement, soit une fonction (ou une méthode) et c'est alors une fonction qui est appelée à chaque séquence trouvée pour traitement particulier. Et, bien sûr, g, argument passé à cette fonction, est là pour identifier la séquence trouvée, qui est entre g.start(0) et g.end(0).

    Dans la méthode __call__, place le print suivant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    print g.start(0), g.end(0), self.ch[g.start(0):g.end(0)]
    Appliqué à l'exemple donné, le traitement donnera:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    10 29 <pre>xx$xx$xx</pre>
    39 50 <pre></pre>
    60 79 <pre>xx$xx$xx</pre>
    On voit que, conformément au motif, toutes les séquences <pre>...</pre> sont trouvées, et à chaque séquence trouvée, la méthode __call__ est appelée. Mais comme ce sont justement les séquences qui ne sont pas modifiées, on utilise self.i (initialisé à 0) pour repérer la chaine qui précède afin de la traiter. Et la méthode "fin" termine pour traiter ce qui se trouve entre la fin de la dernière séquence trouvée et la fin de chaine.

    Sinon, je ne vois vraiment pas pourquoi la méthode __call__ ne serait pas appelée, mais il doit y avoir une raison: donne plus de code représentant ton nouveau contexte, mais simplifié pour qu'on puisse l'exécuter. Par exemple, dans ce que tu viens de donner, il manque traitement.result qui est le résultat final traité.

    Par ailleurs, si les tags sont en majuscules, les séquences ne seront pas trouvées: il faudrait modifier la compilation du motif comme suit:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    motif = re.compile(r'(<pre[^>]*>)(.*?)(</pre>)', re.DOTALL | re.IGNORECASE)

  13. #13
    Membre confirmé Avatar de arnaudk
    Inscrit en
    Février 2009
    Messages
    38
    Détails du profil
    Informations forums :
    Inscription : Février 2009
    Messages : 38
    Par défaut
    Merci pour cette réponse complète.

    Citation Envoyé par tyrtamos Voir le message
    sub attend soit une chaine, et c'est alors une chaine de remplacement, soit une fonction (ou une méthode)
    C'est bien là la première subtilité qui m'a échappée : je considérais traitement toujours comme un objet de la class Sub, et je n'avais pas repéré la nuance avec l'appel de la méthode sub.

    Du coup le code est parfaitement compris.

    Le dernier soucis que j'avais est que dans la méthode __call__, je faisais un remplacement du "$" par un "£", ce qui passait dans un test standalone, mais pas dans un environnement django.

    En utilisant la double esperluette à la place, tout fonctionne.
    Encore merci pour tout

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

Discussions similaires

  1. Réponses: 4
    Dernier message: 20/07/2012, 11h51
  2. Créer Diaporama contenu html sur web pour TV
    Par ant0inep dans le forum Général Conception Web
    Réponses: 0
    Dernier message: 26/10/2011, 11h39
  3. Expression régulière sur un contenu HTML
    Par major68 dans le forum Langage
    Réponses: 2
    Dernier message: 09/07/2009, 23h27
  4. [débutant]travailler sur contenu string
    Par Serge76 dans le forum SL & STL
    Réponses: 13
    Dernier message: 06/11/2004, 16h43
  5. Optimisations mysql sur les requêtes SELECT: index
    Par leo'z dans le forum Débuter
    Réponses: 2
    Dernier message: 29/11/2003, 13h23

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