guide complet · FR
Construire un questionnaire
de zéro, cellule par cellule.
XLSForm est le standard qui transforme un simple tableur en formulaire numérique complet —
avec logique de saut, validation, GPS, photos et plusieurs langues — prêt à tourner sur
ODK Collect, KoboToolbox ou Enketo. Ce guide construit une enquête ménage
pas à pas et documente tous les types de questions et réglages,
avec des exemples à copier et convertir avec rxform.
1 · Anatomie d’un XLSForm
Un XLSForm est un fichier .xlsx (ou .xls/.ods) avec jusqu’à trois feuilles principales :
survey— les questions, dans leur ordre d’apparition. Colonnes minimales :type,nameetlabel.choices— les listes d’options des questions à choix. Colonnes minimales :list_name,nameetlabel.settings— titre, identifiant, version, langue par défaut et autres réglages du formulaire (une seule ligne de données).
Règles d’or pour la colonne name (questions, groupes et options) :
Les noms commencent par une lettre ou _ et ne contiennent que lettres, chiffres, -, _ et . — sans espaces ni accents. Ils deviennent les noms de colonnes de votre base de données : préférez revenu_mensuel à Revenu Mensuel (€). Le label, lui, est du texte libre : accents, emoji, tout ce qu’il faut.
2 · Le premier formulaire
Le plus petit formulaire utile tient en trois lignes de la feuille survey :
| type | name | label | |
|---|---|---|---|
| 2 | text | enqueteur | Nom de la personne qui administre l’enquête |
| 3 | integer | taille_menage | Combien de personnes vivent dans le ménage ? |
| 4 | select_one sim_nao | eau_courante | Le ménage a-t-il l’eau courante ? |
La question de la ligne 4 utilise la liste oui_non, définie dans la feuille choices :
| list_name | name | label | |
|---|---|---|---|
| 2 | oui_non | oui | Oui |
| 3 | oui_non | non | Non |
Et la feuille settings donne son identité au formulaire :
| form_title | form_id | version | |
|---|---|---|---|
| 2 | Enquête Ménage 2026 | enq_menage_2026 | 2026081101 |
Convertissez, et c’est prêt :
$ rxform enquete_menage.xlsx
enquete_menage.xml
3 · Tous les types de questions
Texte et nombres
| type | name | label | hint | |
|---|---|---|---|---|
| 2 | text | observations | Observations générales | Texte libre |
| 3 | integer | age | Âge (années révolues) | Nombres entiers uniquement |
| 4 | decimal | revenu | Revenu mensuel | Point pour les centimes |
| 5 | range | satisfaction | Satisfaction transport | 0 = très mauvais · 10 = excellent |
text— texte libre. Pour une zone plus haute,parameters=rows=5.integer/decimal— nombres, avec le pavé numérique sur l’appareil.range— curseur ; bornes viaparameters=start=0 end=10 step=1. Avecappearance=rating, des étoiles.
Dates et heures
| type | name | label | appearance | |
|---|---|---|---|---|
| 2 | date | date_visite | Date de la visite | |
| 3 | date | emmenagement | Quand avez-vous emménagé ? | month-year |
| 4 | time | heure_debut | Heure de début | |
| 5 | dateTime | rendez_vous | Programmer le retour pour |
Apparences utiles pour date : month-year, year et no-calendar.
Choix et listes
| type | name | label | appearance | |
|---|---|---|---|---|
| 2 | select_one scolarite | scolarite | Niveau d’études du chef de ménage | minimal |
| 3 | select_multiple services | services | Quels services desservent la rue ? | |
| 4 | rank priorites | priorites | Classez les priorités du quartier |
select_one LISTE— une réponse.appearance:minimal(menu déroulant),likert,quick(avance seul),columns…select_multiple LISTE— plusieurs réponses ; stockées séparées par des espaces, d’où l’interdiction d’espaces dans lesnamedes options.rank LISTE— l’enquêté ordonne les options.select_one LISTE or_other— ajoute l’option « Other » et une question automatique « Specify other. ».
Des colonnes supplémentaires dans choices sont permises et deviennent des données de l’option — la base des filtres en cascade :
Sélections en cascade (choice_filter)
| list_name | name | label | uf | |
|---|---|---|---|---|
| 2 | regions | dk | Dakar | |
| 3 | regions | th | Thiès | |
| 4 | communes | plateau | Plateau | dk |
| 5 | communes | medina | Médina | dk |
| 6 | communes | thies_n | Thiès Nord | th |
| type | name | label | choice_filter | |
|---|---|---|---|---|
| 2 | select_one regions | region | Région | |
| 3 | select_one communes | commune | Commune | region = ${region} |
Dans le filtre, les colonnes de la liste s’écrivent par leur nom (region) et les réponses précédentes en ${region}. Pour mélanger les options : parameters = randomize=true (avec seed=42 pour un ordre reproductible).
Localisation
| type | name | label | parameters | |
|---|---|---|---|---|
| 2 | geopoint | point | Localisation du ménage | capture-accuracy=5 warning-accuracy=10 |
| 3 | geotrace | trajet | Trajet jusqu’à l’arrêt de bus | |
| 4 | geoshape | parcelle | Contour de la parcelle |
geopoint capture un point (précision cible en mètres via parameters) ; geotrace, une ligne ; geoshape, un polygone fermé.
Médias et fichiers
| type | name | label | appearance | parameters | |
|---|---|---|---|---|---|
| 2 | image | photo_facade | Photo de la façade | max-pixels=1024 | |
| 3 | image | signature | Signature de l’enquêté | signature | |
| 4 | audio | temoignage | Enregistrez le témoignage | quality=voice-only | |
| 5 | video | video_rue | Vidéo de la rue | ||
| 6 | file | justificatif | Joindre le justificatif (PDF) | ||
| 7 | barcode | code_compteur | Code-barres du compteur d’eau |
image avec appearance = signature devient un champ de signature ; draw, dessin libre. max-pixels réduit la taille des photos sur l’appareil.
Notes et confirmations
| type | name | label | |
|---|---|---|---|
| 2 | note | intro | Bonjour ! Cette enquête prend ~15 minutes. Les réponses sont confidentielles. |
| 3 | acknowledge | consentement | L’enquêté accepte de participer |
note— texte affiché, sans réponse. Accepte les${références}: « Merci, ${enqueteur} ! ». Seul type qui peut omettrename(rxform en génère un).acknowledge— exige un « ok » explicite de l’enquêté.
Champs invisibles
| type | name | calculation | |
|---|---|---|---|
| 2 | calculate | revenu_par_tete | ${revenu} div ${taille_menage} |
| 3 | hidden | version_echantillon |
calculate calcule une valeur en XPath (fonctions if(), concat(), selected(), count(), round()…) ; hidden stocke une valeur remplie par default ou des intégrations.
Métadonnées — collectées toutes seules
| type | name | label / trigger | |
|---|---|---|---|
| 2 | start | debut | |
| 3 | end | fin | |
| 4 | today | aujourdhui | |
| 5 | deviceid | appareil | |
| 6 | username | utilisateur | |
| 7 | audit | audit | |
| 8 | start-geopoint | position_debut | |
| 9 | background-audio | enregistrement | |
| 10 | background-geopoint | position_reponse | trigger : ${eau_courante} |
start/end— horodatage d’ouverture/fin ;today— la date ;deviceid,username,phonenumber,email— identification de l’appareil/du compte.audit— journal d’audit de la saisie ; avecparameters=location-priority=balanced location-min-interval=60 location-max-age=120, il journalise aussi la position pendant l’entretien.start-geopoint— capture silencieuse de la position à l’ouverture ;background-audioenregistre l’audio de l’entretien ;background-geopointcapture la position quand la question dutriggerest renseignée.
4 · Logique du formulaire
Toute la logique utilise ${nom} pour référencer des réponses précédentes.
relevant — sauter des questions
| type | name | label | relevant | |
|---|---|---|---|---|
| 2 | select_one sim_nao | travaille | Travaillez-vous ? | |
| 3 | text | profession | Quelle est votre profession ? | ${travaille} = 'oui' |
| 4 | note | note_age | Module réservé aux majeurs. | ${age} >= 18 |
La question n’apparaît que si l’expression est vraie. Combinez avec and/or ; pour les choix multiples, selected(${services}, 'eau').
constraint — valider les réponses
| type | name | label | constraint | constraint_message | |
|---|---|---|---|---|---|
| 2 | integer | age | Âge | . >= 0 and . <= 120 | L’âge doit être entre 0 et 120. |
| 3 | date | naissance | Date de naissance | . <= today() | La date ne peut pas être future. |
Le point . est la réponse elle-même. Le message s’affiche quand la règle échoue.
required, default, read_only
| type | name | label | required | required_message | default | |
|---|---|---|---|---|---|---|
| 2 | select_one oui_non | eau_courante | Eau courante ? | yes | Cette réponse est obligatoire. | |
| 3 | date | date_visite | Date de la visite | today() | ||
| 4 | integer | secteur | Secteur de recensement | 42 |
required=yes(ou une expression) empêche d’avancer sans réponse.defaultaccepte une valeur fixe (42) ou une expression dynamique (today(),${region}) évaluée à l’ouverture — y compris${last-saved#secteur}pour hériter du dernier envoi.read_only=yesaffiche sans permettre l’édition.
trigger — recalculer au changement
| type | name | label | trigger | calculation | |
|---|---|---|---|---|---|
| 2 | integer | taille_menage | Combien de résidents ? | ||
| 3 | integer | enfants | Combien d’enfants ? | ${taille_menage} |
Avec trigger, le champ est (re)défini chaque fois que la question référencée change — ici, en vidant enfants quand taille_menage est modifié. S’il y a une calculation, elle s’exécute à ce moment-là (au lieu d’en continu).
5 · Groupes, répétitions et boucles
Groupes
| type | name | label | appearance | relevant | |
|---|---|---|---|---|---|
| 2 | begin_group | module_eau | Module : Assainissement | field-list | ${eau_courante} = 'oui' |
| 3 | select_one source_eau | source | Source de l’eau | ||
| 4 | integer | jours_sans | Jours sans eau ce mois-ci | ||
| 5 | end_group |
appearance=field-listaffiche tout le groupe sur un seul écran.table-list— pour une suite deselect_onepartageant une liste, affiche une matrice (lignes × colonnes).- Le
relevantdu groupe s’applique à tout son contenu.
Répétitions
| type | name | label | repeat_count | |
|---|---|---|---|---|
| 2 | begin_repeat | resident | Détails du résident | ${taille_menage} |
| 3 | text | nom | Nom | |
| 4 | integer | age_resident | Âge | |
| 5 | end_repeat |
Le bloc se répète une fois par résident. Sans repeat_count, l’enquêteur ajoute les répétitions à la main ; avec une expression (ou un nombre), le compte est automatique. À l’intérieur, position(..) donne l’indice courant et indexed-repeat() lit les valeurs d’une autre répétition.
Boucles sur une liste
| type | name | label | |
|---|---|---|---|
| 2 | begin loop over services | eval_services | |
| 3 | select_one notes | note | Comment évaluez-vous le service %(label)s ? |
| 4 | end loop |
Génère un bloc par option de la liste services, en substituant %(label)s et %(name)s — une question « Comment évaluez-vous le service Eau ? », une autre « …Assainissement ? », etc.
6 · Plusieurs langues
| type | name | label::Français (fr) | label::English (en) | hint::Français (fr) | |
|---|---|---|---|---|---|
| 2 | integer | moradores | Combien de personnes vivent ici ? | How many people live here? | Comptez tous les résidents |
Il suffit de suffixer les colonnes traduisibles avec ::Langue (code) : label, hint, guidance_hint, constraint_message, required_message, image, audio et video — dans survey et choices. Fixez la langue initiale dans settings → default_language = Français (fr). L’application gagne un menu de langues.
Média par question : les colonnes image/audio/video associent des fichiers (ex. carte_reponse.jpg) envoyés avec le formulaire.
7 · Apparences (appearance)
| appearance | s’applique à | effet |
|---|---|---|
| minimal | select_one/multiple | menu déroulant compact |
| quick | select_one | avance à la sélection |
| likert | select_one | échelle horizontale type Likert |
| columns / columns-n | selects | options en colonnes |
| autocomplete | select_one | recherche à la frappe |
| field-list | groupes | tout le groupe sur un écran |
| table-list | groupes | matrice de selects partageant une liste |
| multiline | text | zone multi-lignes |
| numbers / thousands-sep | text | pavé numérique · séparateur de milliers |
| month-year · year · no-calendar | date | précision réduite · sans calendrier |
| signature · draw · annotate | image | signature · dessin · annoter une photo |
| map · quick map | select_one_from_file (geojson) | choix sur carte |
| rating | range | étoiles |
| label · list-nolabel | selects | briques pour matrices manuelles |
8 · Paramètres (parameters)
La colonne parameters reçoit des paires clé=valeur séparées par des espaces :
| type | paramètres |
|---|---|
| range | start=0 end=10 step=1 |
| text | rows=5 |
| image | max-pixels=1024 |
| audio · background-audio | quality=voice-only | low | normal |
| geopoint | capture-accuracy=5 warning-accuracy=10 |
| selects | randomize=true seed=42 |
| select_*_from_file | value=coluna label=coluna |
| audit | location-priority=balanced location-min-interval=60 location-max-age=120 |
9 · La feuille settings
| colonne | rôle |
|---|---|
| form_title | titre affiché dans l’application |
| form_id | identifiant unique du formulaire sur le serveur |
| version | version (utilisez une date : 2026081101) ; le serveur gère les mises à jour par elle |
| instance_name | nom de chaque envoi dans les listes — ex. concat(${commune}, '-', ${date_visite}) |
| default_language | langue initiale, ex. Français (fr) |
| style | pages (un écran par groupe) · theme-grid |
| public_key | clé RSA pour chiffrer les envois de bout en bout |
| submission_url · auto_send · auto_delete | destination et politique d’envoi |
| allow_choice_duplicates | autorise des name répétés dans une liste |
| clean_text_values | no préserve les espaces multiples dans les cellules (repliés par défaut) |
| name · namespaces · attribute::x · prefix · delimiter · flat · omit_instanceID | réglages avancés du XML généré |
10 · Données externes et entités
Grandes listes en fichier
| type | name | label | choice_filter | |
|---|---|---|---|---|
| 2 | select_one_from_file communes.csv | commune | Commune | region = ${region} |
| 3 | select_one_from_file secteurs.geojson | secteur | Secteur (sur la carte) |
Le CSV requiert des colonnes name et label (ou indiquez-en d’autres avec parameters = value=code label=description) ; le GeoJSON utilise id/title et active appearance = map. Le fichier est téléversé avec le formulaire.
Recherches avec pulldata()
| type | name | calculation | |
|---|---|---|---|
| 2 | calculate | cible_secteur | pulldata('cibles', 'cible', 'secteur', ${secteur}) |
Cherche dans cibles.csv la colonne cible de la ligne où secteur = la réponse. Il y a aussi ${last-saved#champ} (valeur du dernier envoi) et la feuille entities, qui permet au formulaire de créer et mettre à jour des registres partagés entre formulaires (ex. enregistrer des ménages lors d’une visite et les retrouver à la suivante) — via save_to sur les questions et create_if/update_if/label sur la feuille.
11 · Erreurs courantes — et comment rxform prévient
- Type mal orthographié →
[sheet 'survey', row 3, column 'type'] unknown question type 'integr' — did you mean 'integer'? - Référence cassée →
'${agge}' does not match… — did you mean 'age'? - Liste inexistante → pointe la ligne et suggère la liste la plus proche.
- Groupe non fermé → pointe la ligne du
begin_groupet réclame leend_group. - Noms dupliqués entre frères, nom avec espace/accent, question visible sans label, options répétées dans une liste — le tout avec feuille, ligne, colonne et cause probable.
12 · Convertir et publier
# convertir
$ rxform enquete_menage.xlsx
enquete_menage.xml
# vérifier les erreurs d’édition = simplement lancer — le message pointe la cellule
$ rxform enquete_menage.xlsx --stdout > /dev/null
- KoboToolbox : téléversez le
.xlsxlui-même (Kobo convertit côté serveur avec pyxform — rxform produit exactement le même XML, donc il sert de validation locale instantanée) ou déployez le XML via l’API. - ODK Central : publiez le
.xlsxou le.xmlgénéré ; joignez les CSV/GeoJSON des listes externes. - Enketo : les formulaires publiés reçoivent automatiquement un lien web.
Flux recommandé : gardez le .xlsx sous contrôle de version, lancez rxform en CI pour valider chaque changement (la conversion échoue avec un message précis si quelque chose casse) et publiez depuis la version validée.
guide · rxform — retour à l’accueil · référence officielle xlsform.org