-
Notifications
You must be signed in to change notification settings - Fork 1
GOQL.fr
Chaque vue de liste dans Gramps Connect — Individus, Familles, Événements, et ainsi de suite — a un champ de recherche. Par défaut, y taper effectue une correspondance rapide en texte simple sur la poignée de champs courants de cette vue (pour Individus : ID Gramps et nom ; pour Lieu : ID, titre et nom du lieu ; et ainsi de suite) — suffisant pour la plupart des recherches quotidiennes, et le comportement par défaut parce que c'est plus simple qu'un langage de requête.
À côté du champ se trouve une case à cocher : « Utiliser le Gramps Object Query Language ». L'activer, et ce même champ accepte à la place une condition GOQL complète (gramps-object-query-language) — un petit langage construit spécifiquement pour interroger les fiches de Gramps, capable de choses qu'une correspondance en texte simple ne peut absolument pas toucher : atteindre à travers des relations (une jointure, en termes de base de données — « chaque famille dont la mère et le père partagent un nom de famille »), et même remonter à travers elles en sens inverse (« chaque note vers laquelle plus rien ne pointe »). Cette page s'adresse à quiconque veut écrire une de ces conditions — aucune expérience de programmation nécessaire.
GOQL est aussi ce qui fait fonctionner les appels
filter()/db.get_number_of() des Gramplets et
l'argument where= de people()/families()/etc. — la même syntaxe
que l'on taperait dans le champ de recherche avec la case cochée
fonctionne aussi depuis le code Python d'une extension. (db. get_number_of()/l'ancien count() tout court sont des fonctions de
l'API Gramplet pour compter les fiches correspondantes, pas le
any/len propre à GOQL couvert plus bas — une chose différente, le
même nom.)
Vous ne voulez pas du tout l'écrire à la main ? Le bouton « Filtres » à côté du titre de chaque liste propose des dizaines de règles courantes sous forme de simples cases à cocher — combinables avec ET/OU, imbricables, et niables — et construit dessous exactement le même GOQL, affiché en lecture seule pour voir ce qu'il a écrit. Voir la liste des fonctionnalités de la page Aperçu à ce sujet. Son « + Nouvelle règle personnalisée… » est le seul endroit dans cette boîte de dialogue qui accepte encore une condition GOQL brute à la main, même syntaxe que toute cette page — une fois enregistrée et nommée, elle est réutilisée comme une case à cocher ordinaire à partir de là, jamais retapée.
Une requête s'écrit toujours dans le contexte d'une vue — Individu, Famille, Événement, et ainsi de suite — et n'est qu'une seule condition :
surname == 'Smith'Cela se lit comme : dans la vue Individu, trouver les fiches où le nom de famille est égal à 'Smith'. La vue dans laquelle on cherche fixe le type de fiche auquel la condition s'applique.
Quelques symboles reviennent sans cesse :
| Symbole | Signification |
|---|---|
==, !=
|
est égal à / est différent de |
<, <=, >, >=
|
est inférieur à, au plus, supérieur à, au moins (plus tôt/plus tard, plus petit/plus grand) |
and, or, not
|
combiner des conditions, ou en inverser une |
in [ ... ] |
correspond à l'une quelconque d'une liste de valeurs |
'texte' in field |
correspond si field contient 'texte' n'importe où |
like(field, 'motif') |
correspond à un motif de texte, où % signifie « n'importe quoi » |
regex(field, 'motif') |
correspond à une expression régulière, pour ceux qui les connaissent déjà |
Les valeurs textuelles vont entre apostrophes simples ('Smith') ; pas
les nombres (1968).
and exige que les deux côtés soient vrais ; or n'en a besoin que
d'un. Les deux se lisent naturellement : gender == Person.MALE and surname == 'Smith' trouve chaque homme nommé Smith, tandis que
given_name == 'John' or surname == 'Doyle' trouve tous ceux nommés
John ou n'importe qui nommé Doyle. Mélanger les deux fonctionne comme
l'arithmétique ordinaire, où la multiplication passe avant
l'addition — and est vérifié avant or — mais les parenthèses
rendent l'intention claire dans tous les cas : (gender == Person.MALE and surname == 'Smith') or given_name == 'Mary' trouve chaque Smith
masculin, plus quiconque nommé Mary de n'importe quel genre.
not inverse une condition, correspondant chaque fois que ce qu'elle
contient n'est pas vrai : not (surname == 'Smith') désigne tous
ceux dont le nom de famille n'est pas Smith. Les comparaisons peuvent
aussi être enchaînées, comme le permet le vrai Python — Date('Jan 1, 1900') < birth.date.sortval < Date('Jan 1, 1950') trouve tous ceux nés
strictement entre ces deux dates, en une ligne au lieu de deux jointes
par and.
Au-delà d'un simple ==, quelques façons plus souples de faire
correspondre du texte couvrent la plupart des recherches quotidiennes.
given_name in ['John', 'Jane'] correspond à n'importe quel nom de la
liste — en ajouter autant que voulu. 'an' in given_name correspond
n'importe où dans le nom (Jane, Alexander, Susan, …) sans avoir besoin
de caractères génériques. like(field, 'J%') correspond à un motif où
% signifie « n'importe quoi », like(given_name, 'J%') correspond
donc à John, Jane, James, et ainsi de suite. Pour ceux déjà à l'aise
avec les expressions régulières, regex(field, 'motif') est encore
plus puissant — regex(surname, '^[SD]') trouve chaque Smith et Doyle
d'un coup, quelque chose que like(...) ne peut pas exprimer sans
écrire une condition séparée pour chaque lettre de départ.
Un champ n'a pas besoin de résider directement sur la fiche recherchée.
birth et death mènent d'une personne à son événement de naissance
ou de décès, birth.date.sortval est donc la date de cet événement, et
birth.place.title va un pas plus loin, jusqu'au lieu de cet
événement. La même idée fonctionne sur d'autres types de fiches :
father et mother mènent d'une famille au propre fiche Individu de
chaque parent (father.surname == 'Smith'), source mène d'une
citation à ce qu'elle cite, et enclosed_by mène d'un lieu au lieu qui
l'englobe (le comté d'une ville, par exemple — et cela s'enchaîne avec
lui-même, enclosed_by.enclosed_by.title remonte donc de deux
niveaux).
Les deux côtés d'une comparaison peuvent être l'un de ces chemins, pas
seulement une valeur fixe, ce qui rend possible une requête comme
« familles où la mère et le père partagent un nom de famille » :
father.surname == mother.surname. Enchaînement et comparaison se
combinent librement — father.birth.date.sortval < Date('Jan 1, 1850') fait deux pas depuis une famille (vers le père, puis vers son
événement de naissance) pour trouver des générations plus anciennes
sans savoir à l'avance qui elles sont.
Il n'y a pas de limite au nombre de ces sauts qu'un chemin peut
traverser, et chaque saut peut atterrir sur un type de fiche
entièrement différent. En partant de la vue Famille,
father.birth.place.title == 'Chicago, Cook, Illinois, USA' mène de la
famille au propre Individu du père, de là à son Événement de naissance,
et de là au Lieu de cet événement — trois sauts à travers trois types
de fiches différents, se terminant au nom du lieu, tout en une seule
ligne. Le même enchaînement fonctionne depuis n'importe quelle vue,
dans toute combinaison que les relations ci-dessus permettent.
Une personne a exactement un événement de naissance, mais un nombre
quelconque d'enfants, de notes, de citations, ou de médias attachés —
ceux-là ont besoin d'un autre type de vérification.
any(c.given_name == 'Steve' for c in children) correspond à une
famille si un enfant quelconque satisfait la condition ; omettre la
condition (any(n for n in notes)) demande simplement si quelque
chose du tout est attaché, ce qui est comment not any(n for n in notes) trouve les personnes sans note enregistrée. len(...) demande
« combien » plutôt que « au moins un » — len([c for c in children]) > 2 trouve les familles avec plus de deux enfants, et len([c for c in children if c.gender == Person.MALE]) > 1 restreint cela au compte
des fils seulement.
Une collection est cependant aussi loin qu'une seule requête peut
aller dans cette direction. L'enchaînement décrit ci-dessus
(father.birth.place.title) ne fonctionne qu'à travers des relations
qui mènent à exactement une fiche — any/len peuvent dire si quelque
chose à l'intérieur d'une collection correspond à une condition, mais
la requête ne peut alors pas continuer à enchaîner au-delà de cette
correspondance pour atteindre l'une de ses propres fiches liées. Le
père d'une famille a sa propre famille parentale, par exemple — mais
puisqu'une personne peut être enregistrée comme enfant de plus d'une
famille, ce lien est aussi une collection, un pas de plus qu'une
requête ne peut actuellement suivre. « Chaque famille dont le père et
le grand-père sont nés dans le même comté » n'est donc pas quelque
chose que GOQL peut encore exprimer.
Chaque collection ci-dessus mène vers l'extérieur — les propres
enfants d'une famille, les propres notes d'une personne. backlinks
est la seule collection qui mène dans l'autre sens : quelque chose
d'autre dans l'arbre pointe-t-il vers cette fiche ? Elle est
disponible depuis chacun des dix types de fiches, même ceux sans
propres collections vers l'extérieur (une Étiquette n'a pas d'enfants
ni de notes propres, mais beaucoup de fiches peuvent être étiquetées,
la propre condition backlinks de Tag trouve donc les étiquettes que
rien n'utilise) :
not any(bl for bl in backlinks) # rien ne référence cette fiche du tout
any(bl._class == 'Person' for bl in backlinks) # au moins un Individu la référence
any(bl for bl in backlinks) and len([bl for bl in backlinks]) > 1 # référencée par plus d'une choseC'est ce qui rend possible une requête comme « chaque source que plus
personne ne cite » ou « chaque lieu sans événement enregistré à cet
endroit » — not any(bl for bl in backlinks), exécutée dans la vue
Source ou Lieu.
_class est le seul champ qu'une condition backlinks peut tester —
le propre type de fiche du référent ('Person', 'Family', …). Puisque
le référent d'un rétro-lien peut être n'importe lequel des dix types de
fiches à la fois, _class ne peut pas être suivi plus profondément
comme le permettrait une relation normale — il n'y a pas de
_class.primary_name — mais il accepte in/not in contre une liste :
any(bl._class in ['Person', 'Family'] for bl in backlinks)
any(bl._class != 'Media' for bl in backlinks)len([bl for bl in backlinks]) fonctionne exactement comme len(...)
ci-dessus.
Date('...') comprend le texte de date ordinaire, `birth.date.sortval
= Date('Jan 1, 1968')
trouve donc tous ceux nés à cette date ou après. Une chose à savoir :sortvalest toujours un point unique dans le temps, sans qualificatif « vers », « avant » ou « estimé » attaché — une date de naissance saisie comme « avant 1968 » a le *même*sortvalque le simple « Jan 1, 1968 », une comparaison>=compterait donc cette personne comme née à cette date ou après, même si « avant » signifie le contraire. Quand cette distinction compte, comparer plutôtbirth.date.modifierà une constante nommée, commeDate.MOD_ABOUT`.
Certains champs, comme le genre d'une personne ou la confiance d'une
citation, se comparent à une constante nommée plutôt qu'à un nombre
brut — gender == Person.MALE ou confidence >= Citation.CONF_HIGH —
tirées directement de Gramps lui-même pour qu'elles ne dérivent jamais
de ce que Gramps stocke réellement.
GOQL est un petit ensemble fixe de briques de base, pas un langage de programmation complet — tout ce qui sort des motifs ci-dessus est rejeté avec une erreur plutôt que deviné.
Cette page couvre les motifs quotidiens ; le propre bouton « i » du
champ de recherche, à côté du champ de recherche de chaque vue, liste
les champs et relations exacts disponibles pour cette vue. Pour la
référence de syntaxe complète — chaque opérateur, relation, collection
et cas particulier, avec le raisonnement derrière chacun — voir
where_expr.md
dans le dépôt
gramps-object-query-language,
le projet sur lequel GOQL est construit.
Gramps Connect is part of the family of Gramps-based software.
Using the app
- Overview
- Installing
- Deploying
- Messaging
- GOQL (advanced search)
- Gramplets & Add-on Store
- Data Model & Editing
- FAQ
Building & contributing