#/usr/bin/env python # -*- coding: utf8 -*- # # filter.py --- Workaround with ldap filters # # Redraw filters like attr1=val1&attr2=val2|attr3=val3 # in (|(&(attr1=val1)(attr2=val2))(attr3=val3)) # # Copyright (C) 2010-2013 Cr@ns # Author: Pierre-Elliott Bécue # # # License: WTFPL def human_to_ldap(filtre): """ Transforme les filtres "human readables" en filtres respectant la syntaxe LDAP. """ # Piles, quand on croise une parenthèse ouvrante # on change de pile stacks = {0:''} # Position dans la pile pos = 0 # operateur \in {&, |, &|, } operateur = "" # La pile externe, qu'on merge à la pile en cours quand c'est utile. ext_stack = "" # Élement de la forme paiement=2012 ou retour de la pile # supérieure (quand on croise une parenthèse fermante, la # pile est dumpée dans argument) # Est systématiquement dumpé dans ext_stack quand on croise # un opérateur. argument = "" # Y a-t-il un ! dans la salle ? neg = False # Quand on quitte une pile parenthésée, on veut savoir quel était # l'opérateur actif avant, pour que le comportement de la fonction # soit défini. anciens_operateurs = [] for char in filtre: if char == "(": # Une nouvelle stack ne démarre que si le dernier argument a été dumpé # dans l'ext stack (si ce n'est pas le cas quand on respecte la syntaxe # des filtres, la fonction est mal codée, donc on plante). if argument != "": raise ValueError('Argument entammé non terminé !') # Un dumpe ext_stack dans la stack en cours. stacks[pos] += "%s" % (ext_stack) # On augmente le conteur de la stack pos = pos + 1 # On (ré)?initialise la stack stacks[pos] = '' # On stocke l'opérateur en cours dans la stack inférieure dans # une liste. anciens_operateurs.append(operateur) # On flush operateur operateur = "" # On flush ext_stack, l'environnement est enfin propre pour # bosser dans la nouvelle pile. ext_stack = "" elif char == ")": # Cas classique if operateur == "|": ext_stack += "(%s)" % argument # Moins classique, &| est un opérateur servant à dire qu'on # a déjà croisé un | dans la stack en cours, le cas échéant, # celui-ci doit être pris en compte, pour respecter la # priorité du &. Dans ce cas seulement, une parenthèse # est ouverte et non fermée (cf elif char == '&' cas # operateur == "|"), on la ferme ici. elif operateur == "&|": argument += ")" ext_stack += "(%s)" % argument # Classique elif operateur == "&": ext_stack += "(%s)" % argument # Pas d'opérateur, pas de parenthèse superflue. else: ext_stack += "%s" % argument # On passe la stack en argument, les parenthèses # l'encadrant seront placées d'un qu'un opérateur # sera croisé. argument = "%s%s" % (stacks[pos], ext_stack) # Ménage stacks[pos] = "" ext_stack = "" pos = pos - 1 # Retour à l'opérateur de la stack précédente. operateur = anciens_operateurs.pop() elif char == "|": if not argument: raise ValueError('Aucun argument') # Ce cas permet d'éviter une répétition de la forme : # (|(a)(|(b)(c))), quand on est déjà dans un ou, on # rajoute juste l'argument suivant sans remettre de # symbole. if operateur == "|": # neg est True si on a croisé un ! dans la chaîne. # À améliorer pour qu'il ne marche que pour != if neg: argument = "!(%s)" % argument neg = False # Ajout à la stack externe de l'argument ext_stack += "(%s)" % argument argument = "" elif operateur == "&": if neg: argument = "!(%s)" % argument neg = False ext_stack += "(%s)" % argument # | prend le relais sur &, on dumpe ext_stack et on commence une nouvelle # chaîne qui sera ajoutée à droite. Le ou va tout à gauche de la stack # en cours, pour s'appliquer sur tout son contenu. stacks[pos] = "%s(%s%s)" % (char, stacks[pos], ext_stack) ext_stack = "" argument = "" operateur = "|" # C'est un & dans un |, donc on ferme juste la chaîne # des &, d'où la parenthèse fermante en trop. elif operateur == "&|": if neg: argument = "!(%s)" % argument neg = False ext_stack += "(%s)" % argument argument = "" stacks[pos] = "%s%s)" % (stacks[pos], ext_stack) ext_stack = "" operateur = "|" # Pas encore d'opérateur annoncé elif operateur == "": if neg: argument = "!(%s)" % argument neg = False ext_stack += "%s" % argument argument = "" # ouverture stacks[pos] = "%s(%s%s)" % (char, stacks[pos], ext_stack) ext_stack = "" operateur = "|" else: raise TypeError('Erreur d\'opérateur.') elif char == "&": if not argument: raise ValueError('Aucun argument') if operateur == "&": if neg: argument = "!(%s)" % argument neg = False ext_stack += "(%s)" % argument argument = "" # Le cas spécial, on ouvre un & après un |, donc pour respecter # la priorité de &, on démarre une nouvelle chaîne, mais dans # l'ext_stack (contrairement à char == '|', operateur == "&") elif operateur == "|": if neg: argument = "!(%s)" % argument neg = False ext_stack += "(%s(%s)" % (char, argument) argument = "" operateur = "&|" # On était déjà dans un &|... elif operateur == "&|": if neg: argument = "!(%s)" % argument neg = False ext_stack += "(%s)" % argument argument = "" # Comme ci-dessus elif operateur == "": if neg: argument = "!(%s)" % argument neg = False ext_stack += "%s" % argument argument = "" stacks[pos] = "%s(%s%s)" % (char, stacks[pos], ext_stack) ext_stack = "" operateur = "&" else: raise TypeError('Erreur d\'opérateur.') elif char == "!": neg = True # Remplissage d'argument else: argument += char # Décommenter pour débug. # En modifiant un peu, encore plus utile pour savoir ce qu'il # fait à chaque étape ! #print stacks #print pos, argument, ext_stack, operateur if pos > 0: raise Exception("Tu ne sais pas parenthéser, crétin.") else: # Comme parenthèse fermante. if neg: argument = "!(%s)" % argument # Surtout ça if operateur == "&|": argument += ')' ext_stack += "(%s)" % argument argument = "" stacks[pos] = "(%s%s)" % (stacks[pos], ext_stack) ext_stack = "" # On retourne la pile de plus haut niveau return stacks[0]