l'ensemble des filtres précédents et probablement un ou deux que je n'avais pas publié (je pense notamment à la recherche de doublons dans les lieux
ou le filtre qui affiche les objets de la catégorie en fonction de la date de modification
Code: Alles auswählen
##################################################################################################################
#
# Include ST_filters_init.py
# Filtres Supertool complémentaires à ceux de Gramps
#
# Usage: @include ST_filters_init.py (dans Supertool Initial Statements)
#
# Version: # V1.3
#
# Changes:
# - V1.3:
# - Généralisation de la possibilité d'utiliser un filtre Gramps en ajoutant l'argument facultatif objet=obj à
# chaque méthode "recherche"
# - V1.2:
# - Ajout de la classe ST_Spaces et transfert de la méthode _place_citators de la classe
# STF_source_citations_in_objects vers celle-ci
# - V1.1:
# - Délégation des recherches vers la classe ST_Filters
# - V1.0:
# - Regroupement de tous les fichiers individuels de filtres Supertool en classes au sein de ce fichier unique
#
# Author: Patrice Legoux <Twitter: @plegoux>
#
##################################################################################################################
'''Include des classes d'utilisation des filtres SuperTool
SuperTool filters classes include
Filtres sources / Source filters:
- STF_source_citations :
Filtre les sources ayant la citation <page> si <existe> est vrai, ou toutes celles qui n'ont pas cette citation
si <existe> est faux
- STF_source_citations_in_objects :
Filtre les sources ayant la citation <page> si cette citation est associée à un des lieux <ids>
- STF_depot_support_type :
Filtre les sources ayant le type de support <type_support> parmi leurs références de dépôt
Filtres individus / Person filters:
- STF_role_in_event :
Filtre les individus porteurs du rôle <role> dans leurs événements personnels de type <type>
Filtres événements / Event filters:
- STF_participant_role:
Filtres lieux / Place filters:
- STF_dupplicate_place:
Filtres communs à différents types d'objets / Common filters:
- STF_refattr :
- STF_attrs :
- STF_note_type
Filtres généralistes / General filters:
- STF_dtf :
- STF_refcount_is :
'''
from datetime import date
from abc import ABC, abstractmethod
from collections.abc import Callable
from supertool_engine import PlaceProxy
ST_FILTER = "ST Filter"
ST_GET = "GET"
ST_ASK = "ASK"
ST_TODAY = "TODAY"
ST_ON = "ON"
ST_AFTER = "AFTER"
ST_PERSON = "Person"
ST_EVENT = "Event"
ST_FAMILY = "Family"
ST_CITATION = "Citation"
ST_SOURCE = "Source"
ST_REPOSITORY = "Repository"
ST_NOTE = "Note"
ST_PLACE = "Place"
ST_MEDIA = "Media"
ST_FR_NS = {
"Individu": ST_PERSON,
"Evénement": ST_EVENT,
"Famille": ST_FAMILY,
"Citation": ST_CITATION,
"Source": ST_SOURCE,
"Dépôt": ST_REPOSITORY,
"Note": ST_NOTE,
"Lieu": ST_PLACE,
"Média": ST_MEDIA
}
ST_LESS_THAN = "less than"
ST_GREATER_THAN = "greater than"
ST_EQUAL_TO = "equal to"
# Classes d'exceptions
class Error(Exception):
"""Base class for other excpetions"""
def __str__(self):
return f'{ST_FILTER} exception: {self.message}'
class FilterNotProvidedInNamespace(Error):
"""Raised when a filter is called from a namespace it is not intented for"""
def __init__(self, caller, namespace, auth_ns):
self.caller = caller
self.namespace = namespace
self.auth_ns = auth_ns
self.message = f'Filter not provided in {namespace} namespace' + '\n' + f'{caller} is intended to be used only in {auth_ns} namespace(s)'
super().__init__(self.message)
class SearchFilterNotProvided(Error):
"""Raised when a filter want to search into a namespace not provided"""
def __init__(self, caller, type, auth_ns):
self.caller = caller
self.type = type
self.auth_ns = auth_ns
self.message = f'{type} search filter not provided' + '\n' + f'{caller} is intended for searching only into {auth_ns} namespace(s)'
super().__init__(self.message)
# Classes génériques
class ST_Filters(ABC):
'''Classe d'abstraction des différents filtres'''
_ns: list = []
def __init__(self, auth_ns: list, caller: object) -> None:
self._ns_checked = False
self._auth_ns = auth_ns
self._caller = caller
self.gid = Gid()
def _checkns(self, namespace: list) -> [bool, Exception]:
if self._ns_checked:
return True
self._ns_checked = namespace in self._auth_ns
if not self._ns_checked:
raise FilterNotProvidedInNamespace(self._caller, namespace, self._auth_ns)
@abstractmethod
def recherche(self, namespace: str, gramps_id: str) -> bool:
self._checkns(namespace)
if self._filtre and not self._filtre(self._objet):
return False
return self.gid._addGidIf(gramps_id, self._search_method, *self._search_args)
class ST_Spaces:
"""Moyens d'accès aux objets d'autres espaces
Access methods to other spaces objects
"""
def _place_citators(self, handle, ic):
# derived from original @kku import and function - https://github.com/Taapeli/isotammi-addons/issues/1
for _, rhandle in db.find_backlink_handles(handle, include_classes=[ic]):
yield PlaceProxy(db, rhandle)
class Gid:
'''Stockage et restitution des Gramps ID'''
def __init__(self) -> None:
self._gid = []
def __iter__(self):
return iter(self._gid)
def __next__(self):
return next(self._gid)
def _addGidIf(self, gid: str, method, *args) -> bool:
"""Méthode de stockage d'un Gramps ID par l'appel d'une méthode retournant un booléin.
Arguments:
- gid : Gramps ID qui sera stocké si la méthode retourne True
- method : une méthode ou fonction à laquelle sont passés les arguments args. Si le retour est True, gid sera stocké
- *arg : liste d'arguments qui est passée à method
Retourne:
- True ou False en fonction du retour de method
"""
if method(*args):
self.append(gid)
return True
else:
return False
def append(self, gid: str) -> None:
# Méthode définie pour rester compatible avec la V0.0
if gid not in self._gid:
self._gid.append(gid)
# Classes de filtres sources
class STF_source_citations(ST_Filters):
'''STF_source_citations - Filtre les sources ayant la citation <page> si <existe> est vrai, toutes celles qui n'ont pas cette sitation s'il est faux
Rule:
gramps_id in rcitation.gid
Initial statements:
@include ST_filters_init.py
rcitation = STF_source_citations(
# Valeurs modifiables - début
page = "", # Citation Page (ex.: "Acte de décès")
existe = True #valeurs possibles: True / False, en fonction retourne toutes les sources avec ou sans la citation contenant la valeur dans page
# Valeurs modifiables - fin
)
Statements:
rcitation.recherche(namespace=namespace, gramps_id=gramps_id, citations=citations)
Exemples:
- nom du filtre Gramps: S90.158p: 90. CitationPage["_Objet de la source: Histoire de Saint-Chinian de la Corne et de ses environs (Hérault)"] - 158p
- contenu du fitre supertool "Generic filter rule" inclu dans le fitre Gramps:
Rule:
gramps_id in rcitation.gid
Initial statements:
@include ST_filters_init.py
rcitation = STF_source_citations(
# Valeurs modifiables - début
page = "_Objet de la source: Histoire de Saint-Chinian de la Corne et de ses environs (Hérault)",
existe = True #valeurs possibles: True / False, en fonction retourne toutes les sources avec ou sans la citation contenant la valeur dans page
# Valeurs modifiables - fin
)
Statements:
rcitation.recherche(citations=citations, gramps_id=gramps_id, namespace=namespace)
'''
_ns: list = [ST_SOURCE]
def _recherche_1(self, citations: list) -> bool:
add_page: bool = False
for citation in citations:
if self._page in citation.page:
add_page = True
break
return(self._existe and add_page) or (not self._existe and not add_page)
def __init__(self, page: str, existe: bool, filtre: object = None) -> None:
self._page: str = page
self._existe: bool = existe
self._filtre = filtre
self._search_method = self._recherche_1
super().__init__(self._ns, self.__class__.__name__)
def recherche(self, namespace: str, gramps_id: str, citations: list, objet = None) -> bool:
self._objet = objet
self._search_args = [citations]
return super().recherche(namespace, gramps_id)
class STF_source_citations_in_objects(ST_Filters, ST_Spaces):
'''STF_source_citations_in_objects - Filtre les sources ayant la citation <page> si cette citation est associée à un des lieux <ids>
Rule:
gramps_id in rplaceCitation.gid
Initial statements:
@include ST_filters_init.py
rplaceCitation = STF_source_citations_in_objects(
o_type = ST_PLACE,
# Valeurs modifiables - début
o_ids = [], # Place ID list (ex: ["P00001", "P00002"])
c_page = "" # Citation Page (ex.: "Acte de décès")
# Valeurs modifiables - fin
)
Statements:
rplaceCitation.recherche(namespace=namespace, gramps_id=gramps_id, citations=citations)
Exemples:
- S90.157p: 90. Citations['_Objet de la source:'].References[{'Lieu': 'FR-61446, Saint-Pierre-des-Loges"}] - 157p
'''
# note à moi-même: il y probablement moyen d'hériter de STF_source_citations (ou de n'utiliser qu'elle) à condition de réécrire ces deux classes
_ns: list = [ST_SOURCE]
_object_ns: list = [ST_PLACE]
def _recherche_1(self, citations: list) -> bool:
for citation in citations:
for referent in self._place_citators(citation.handle, self._type):
for id in self._ids:
if f'{self._type}[{id}]' in str(referent) and self._page in citation.page:
return True
return False
def __init__(self, o_type: str, o_ids: list, c_page: str, filtre: object = None) -> None:
if o_type not in self._object_ns:
raise SearchFilterNotProvided(self.__class__.__name__, o_type, self._object_ns)
else:
self._type = o_type
self._ids = o_ids
self._page = c_page
self._filtre = filtre
self._search_method = self._recherche_1
super().__init__(self._ns, self.__class__.__name__)
def recherche(self, namespace: str, gramps_id: str, citations: list, objet = None) -> bool:
self._objet = objet
self._search_args = [citations]
return super().recherche(namespace, gramps_id)
class STF_depot_support_type(ST_Filters):
'''STF_depot_support_type - Filtre les sources ayant le type de support <type_support> parmi leurs références de dépôt
Rule:
gramps_id in rsupportType.gid
Initial statements:
@include ST_filters_init.py
rsupportType = STF_depot_support_type(
# Valeurs modifiables - début
type_support = 'Électronique'
# Valeurs modifiables - fin
)
Statements:
rsupportType.recherche(source=source, gramps_id=gramps_id, namespace=namespace)
Exemples:
- S90.149p: 90. DépotSupport['Électronique'] - 149p
'''
_ns: list = [ST_SOURCE]
_recherche_1 = lambda self, source: [(repo.media_type.string) for repo in source.reporef_list] == [self._type_support]
def __init__(self, type_support: str, filtre: object = None) -> None:
self._type_support = type_support
self._filtre = filtre
self._search_method = self._recherche_1
super().__init__(self._ns, self.__class__.__name__)
def recherche(self, namespace: str, gramps_id: str, source: list, objet = None) -> bool:
self._objet = objet
self._search_args = [source]
return super().recherche(namespace, gramps_id)
# Classes de filtres individus
class STF_role_in_event(ST_Filters):
'''STF_role_in_event - Filtre les individus porteurs du rôle <role> dans leurs événements personnels de type <type>
Rule:
gramps_id in rroleEvenPerso.gid
Initial statements:
@include ST_filters_init.py
rroleEvenPerso = STF_role_in_event(
# Valeurs modifiables - début
type = "", # Type d'événement (ex. "Birth")
"" # Rôle dans l'évéement (ex. "Déclarant")
# Valeurs modifiables - fin
)
Statements:
rroleEvenPerso.recherche(namespace=namespace, gramps_id=gramps_id, events=events)
'''
_ns: list = [ST_PERSON] # en attendant les lieux
_recherche_1 = lambda self, et, er: et == self.evenement and er == self.role
def __init__(self, evenement: str, role: str, filtre: object = None) -> None:
self.evenement = evenement
self.role = role
self.filtre = filtre
self.search_method = self._recherche_1
super().__init__(self._ns, self.__class__.__name__)
def recherche(self, namespace: str, gramps_id: str, events: list, objet = None) -> bool:
self._objet = objet
for event in events:
self.search_args = [event.type, event.role]
search_result = super().recherche(namespace, gramps_id)
if search_result:
return search_result
return False
# Classes de filtres événements
class STF_participant_role(ST_Filters):
'''STF_participant_role - Filtre les événements du type <event_type> dont les individus ou les familles qui y participent on pour rôle <participant_role>. Facultativement, le filtre ne prend en compte que le sous-ensemble des événements correspondant au filtre Gramps <filtre>.
Rule:
gramps_id in rtypeRole.gid
Initial statements:
@include ST_filters_init.py
rtypeRole = STF_participant_role(
# Valeurs modifiables - début
event_type = "", # Type d'événement (ex. "Birth")
participant_role = "Déclarant", # Rôle des participants dans l'événement (ex. "Déclarant")
filtre = <valeurs_possibles> # Valeurs possibles:
# - None
# - filter(<nom du filtre événements Gramps>)
# Valeurs modifiables - fin
)
Statements:
rtypeRole.recherche(namespace=namespace, gramps_id=gramps_id, participants=participants, type=type, objet=obj)
Exemples:
- Initial statements avec l'appel d'un filtre événements Gramps:
@include ST_filters_init.py
rtypeRole = STF_participant_role(
# Valeurs modifiables - début
event_type = "Birth",
participant_role = "Déclarant",
filtre = filter("90. Individus.Signets.EvFamiliaux[Exclus] - 045p")
# Valeurs modifiables - fin
)
- Initial statements sans l'appel d'un filtre événements Gramps:
@include ST_filters_init.py
rtypeRole = STF_participant_role(
# Valeurs modifiables - début
event_type = "Birth",
participant_role = "Déclarant",
filtre = None
# Valeurs modifiables - fin
)
'''
_ns: list = [ST_EVENT]
def _recherche_1(self, participants, type, objet):
for part in participants:
for pe in part.events:
if pe == objet and pe.role == self._participant_role and type == self._event_type:
return True
return False
def __init__(self, event_type: str, participant_role: str, filtre: object = None) -> None:
self._event_type = event_type
self._participant_role = participant_role
self._filtre = filtre
self._search_method = self._recherche_1
super().__init__(self._ns, self.__class__.__name__)
def recherche(self, namespace: str, gramps_id: str, participants, type, objet: object) -> bool:
self._objet = objet
self._search_args = [participants, type, objet]
return super().recherche(namespace, gramps_id)
# Classes de filtres lieux
class STF_dupplicate_place(ST_Filters):
"""STF_dupplicate_place - Recherche les éventuels lieux duppliqués
A faire tourner dans la vue Lieux et voir lieu par lieu s'il y a un doublon dans la vue Groupe de lieux
"""
_ns: list = [ST_PLACE]
_recherche_1 = lambda self, x, y: x in y
_recherche_2 = lambda self, x: x
def __init__(self, filter_type: bool, filtre: object = None) -> None:
self._filter_type = filter_type
self._ln = {}
self._filtre = filtre
super().__init__(self._ns, self.__class__.__name__)
def recherche(self, namespace: str, gramps_id: str, longname: str, type: str, objet = None) -> None:
self._objet = objet
self._search_method = self._recherche_1
lnt = (longname, type) if self._filter_type else longname
self._search_args = [lnt, self._ln]
if super().recherche(namespace, gramps_id):
self._search_method = self._recherche_2
self._search_args = [self._ln[lnt][0]]
if super().recherche(namespace, self._ln[lnt][1]):
self._ln[lnt][0] = False
else:
self._ln[lnt] = [True, gramps_id]
# Classes de filtres multi-objets
class STF_refattr(ST_Filters):
"""STF_refattr - Filtre les objets ayant dans ses références à d'autres objets (par ex. une personne dans ses références aux événements ou un lieu dans ses références aux médias) les attributs <attribut_type> et/ou <attribut_value>"""
_ns: list = [ST_PERSON, ST_PLACE, ST_SOURCE, ST_CITATION, ST_FAMILY, ST_EVENT]
_supported_objet_type: list = [ST_EVENT, ST_MEDIA]
def _recherche_1(self, objets: list) -> bool:
# Exécute la recherche des arguments recherchés dans les attributs
#
# Arguments attendus:
# - obj_ref_list: objet "attribute_list" dans lequel chercher la correspondance
# - gramps_id: ID Gramps a ajouter à la liste "gid" d'éléments comportant l'attribut
#
# Retourne:
# - True si la recherche a aboutie
# - False si la recherche a échouée
trouve = False
for objet in objets:
if not self._selected_obj(objet):
continue
trouve = self._ref_attr(objet.attribute_list)
if trouve:
break
return trouve
def __init__(self, attribut_type: str, attribut_value: str, objet_type: str, type_event: str, rech_exacte: bool = False, filtre: object = None) -> None:
caller = self.__class__.__name__
self._attribut_type: str = attribut_type
self._attribut_value: str = attribut_value
self._rech_exacte = rech_exacte
if objet_type not in self._supported_objet_type:
raise Exception(f'PLX FILTERS - {caller} facilities not provided for {objet_type} type', f'{caller} is only intended for {self._supported_objet_type} type(s)')
self._objet_type: str = objet_type
self._type_event: str = type_event if self.objet_type == ST_EVENT else None
self._filtre = filtre
self._search_method = self._recherche_1
super().__init__(self._ns, caller)
def recherche(self, namespace: str, gramps_id: str, objet = None) -> bool:
self._objet = objet
if self._objet_type == ST_EVENT:
#Recherche dans les événements d'une personne ou d'une famille (et à l'avenir d'un lieu)
self._search_args = [objet.get_event_ref_list()]
elif self._objet_type == ST_MEDIA:
#Recherche dans les médias d'une personne, d'une famille, d'une source', d'une citation ou d'un lieu
self._search_args = [objet.get_media_list()]
return super().recherche(namespace, gramps_id)
def _selected_obj(self, obj_rech):
if self._objet_type == ST_MEDIA or (self._objet_type == ST_EVENT and self._type_event == None):
# Sont sélectionnés d'office tous les médias
# ainsi que tous événements si le type d'événement à selectionner n'est pas renseigné
return True
event = db.get_event_from_handle(obj_rech.ref)
if self._type_event[0] == '!':
return event.type != self._type_event[1:]
else:
return event.type == self._type_event
def _ref_attr(self, attr_list: list) -> bool:
# Recherche dans les attributs de référence si le type et/ou la valeur correspondent aux arguments
#
# Arguments attendus:
# - attr_list: objet attribute_list provenant des EventRef, MediaRef, Event, Family, Citation, Source
#
# Retourne:
# True: si attribut trouvé, ou si attr_type et attr_value valent None et que attr_list est vide
# False: dans les cas contraires
if not (self._attribut_type or self._attribut_value or self._rech_exacte):
# le cas où on ne recherche rien (erreur d'arguments; tous à None/False?)
return False
if not (self._attribut_type and self._attribut_value) and (self._rech_exacte and len(attr_list) == 0):
# recherche d'une liste d'attributs vide, trouvée
return True
for attr in attr_list:
# les possibilités restantes
if self._attribut_type and self._attribut_value:
# recherches sur le type et la valeur de l'attribut
if self._rech_exacte and attr_type == attr.type and self._attribut_value == attr.value:
# le type et la valeur de l'attribut recherchés correspondent
return True
if not self._rech_exacte and self._attribut_type == attr.type and (self._attribut_value in attr.value or self._attribut_value == "*"):
# le type correspond et la valeur recherchée fait partie de la valeur de l'attribut
return True
if self._attribut_type and not self._attribut_value:
if self.rech_exacte and self.attribut_type == attr.type:
# le type de l'attibut correspond
return True
if not self._rech_exacte and self._attribut_type in attr.type:
# la valeur recherchée fait partie du type de l'attribut
return True
if not self._attribut_type:
# recherche sur la valeur seule de l'attribut
if self._rech_exacte and self._attribut_value == attr.value:
# la valeur de l'attribut recherchés correspond
return True
if not self._rech_exacte and self._attribut_value in attr.value or self._attribut_value == "*":
# la valeur recherchée fait partie de la valeur de l'attribut
return True
class STF_attrs(ST_Filters):
"""STF_attrs - Filtre les objets ayant sur un attribut et/ou sa valeur"""
_ns: list = [ST_SOURCE, ST_PERSON, ST_EVENT, ST_FAMILY, ST_CITATION, ST_MEDIA]
_recherche_1 = lambda self, attribut_type, type, attribut_valeur, valeur: attribut_type == type and attribut_valeur == valeur
_recherche_2 = lambda self, attribut, attribut_type, attribut_valeur: attribut == (attribut_type, attribut_valeur)
_STAR = '*'
def __init__(self, type: str, valeur: str, filtre: object = None) -> None:
self._type = type
self._valeur = valeur
self._filtre = filtre
super().__init__(self._ns, self.__class__.__name__)
def recherche(self, namespace: str, gramps_id: str, attributes: list, objet = None) -> bool:
self._objet = objet
for attribute in attributes:
self._search_method = self._recherche_1
self._search_args = [self._type, self._STAR, self._valeur, self._STAR]
if super().recherche(namespace, gramps_id):
return True
self._search_args = [self._type, self._STAR, self._valeur, attribute[1]]
if super().recherche(namespace, gramps_id):
return True
self._search_args = [self._type, attribute[0], self._valeur, self._STAR]
if super().recherche(namespace, gramps_id):
return True
self._search_method = self._recherche_2
self._search_args = [attribute, self._type, self._valeur]
if super().recherche(namespace, gramps_id):
return True
return False
class STF_note_type(ST_Filters):
"""STF_note_type - Filtres les objets ayant des notes avec le type <note_type>"""
_ns: list = [ST_EVENT, ST_CITATION, ST_SOURCE, ST_REPOSITORY, ST_PERSON, ST_PLACE]
_recherche_1 = lambda self, nid, nt: db.get_note_from_gramps_id(nid).type == nt
def __init__(self, note_type: str, filtre: object = None) -> None:
self._note_type = note_type
self._filtre = filtre
self._search_method = self._recherche_1
super().__init__(self._ns, self.__class__.__name__)
def recherche(self, namespace: str, gramps_id: str, notes: list, objet = None) -> bool:
self._objet = objet
for note in notes:
self._search_args = [note.gramps_id, self._note_type]
if super().recherche(namespace, gramps_id):
return True
return False
# Classes de filtres généralistes
class STF_dtf(ST_Filters):
"""STF_dtf - Filtre les objets modifiés suivant une date précisée ou à la demande"""
_ns: list = ["Person", "Source", "Event", "Citation", "Repository", "Note", "Place", "Media", "Family"]
def _changeOn(self, change_timestamp): # _recherche_1
return str(date.fromtimestamp(change_timestamp)) == self._a_day
def _changeAfter(self, change_timestamp): # _recherche_2
return str(date.fromtimestamp(change_timestamp)) > self._a_day
def __init__(self, method=ST_TODAY, a_day=None, filtre: object = None):
if method == ST_TODAY:
self._a_day = str(today())
elif method == ST_GET:
self._a_day = a_day
elif method == ST_ASK:
a_day = getargs(daytoSearch="Searched date (default today)")
if a_day.daytoSearch:
self._a_day = a_day.daytoSearch
else:
self._a_day = str(today())
self._filtre = filtre
super().__init__(self._ns, self.__class__.__name__)
def recherche(self, obj, namespace, gramps_id, when=ST_ON, objet = None) -> bool:
self._objet = objet
self._search_method = self._changeOn if when == ST_ON else self._changeAfter
self._search_args = [obj.change]
return super().recherche(namespace, gramps_id)
class STF_refcount_is(ST_Filters):
"""STF_refcount_is - Filtre les objets qui ont un nombre donné de références"""
_ns: list = ["Person", "Source", "Event", "Citation", "Repository", "Note", "Place", "Media", "Family"]
def _recherche_1(self, handle: str) -> bool:
count = len(db.find_backlink_handles(handle))
if self._count_type == ST_LESS_THAN:
return count < self._nb_obj
elif self._count_type == ST_GREATER_THAN:
return count > self._nb_obj
return count == self._nb_obj # "equal to"
def __init__(self, nb_obj: int, count_type: str, filtre: object = None) -> None:
self._nb_obj = nb_obj
self._count_type = count_type
self._filtre = filtre
super().__init__(self._ns, self.__class__.__name__)
def recherche(self, namespace: str, gramps_id: str, obj_to_count: list, objet = None) -> bool:
self._objet = objet
for tc in obj_to_count:
self._search_args = [tc.handle]
if super().recherche(namespace, gramps_id):
return True
return False
(faites attention de ne pas supprimer ou ajouter d'espaces aux lignes) que vous placez dans le répertoire des include Supertool (voir plus haut dans ce fil à propos de ce répertoire).
Au début (normalement) de chaque filtre (class <nom du filtre>) j'ai indiqué son titre et dans un commentaire son usage et comment s'en servir. Exemple:
Si je le modifie, ce qui est fort possible, je le republierai désormais en entier en changeant sa version.