Convertir entre formats de liste sans perdre de données : les règles de guillemets que personne ne lit
Publié le 30/06/2025 · 14 min de lecture · Outils texte & langage
Daniel Okonkwo — Développeur front-end et rédacteur Tech chez OneKitly
Performance web · Formats de fichiers
Vérifié à partir de 6 sources
Passer d'une liste à retours à la ligne à une liste à virgules est une ligne de code jusqu'à ce qu'un élément contienne une virgule. Trois noms — Dupont, Marie / Martin, Jean / D’Arcy, Léa — joints par des virgules puis redécoupés donnent six éléments, pas trois. Le remède est une règle de guillemets, et c'est la RFC 4180 qu'il faut suivre : un champ contenant une virgule, un guillemet double ou un saut de ligne doit être encadré de guillemets doubles, et un guillemet double à l'intérieur d'un champ encadré s'échappe en le doublant. Écrit ainsi, "Dupont, Marie","Martin, Jean","D’Arcy, Léa" se relit en exactement trois éléments. Deux conséquences surprennent. Un champ CSV peut légalement contenir un saut de ligne : un fichier de deux enregistrements peut occuper trois lignes physiques, et le découper sur le caractère de fin de ligne est faux — les enregistrements sont séparés par CRLF et seul un vrai analyseur sait quels sauts comptent. Et une chaîne vide n'est pas zéro élément : découper « » sur la virgule rend un élément vide, tandis que joindre [] et joindre [""] donnent tous deux la chaîne vide, si bien que ces deux listes deviennent indiscernables si l'on ne met pas tous les champs entre guillemets. Ne convertis jamais par un simple découpage sur le délimiteur.
Passer d'une liste à retours à la ligne à une liste à virgules est trivial jusqu'à ce qu'un élément contienne une virgule. Les règles de guillemets de la RFC 4180, pourquoi un champ CSV peut contenir un saut de ligne, pourquoi les tableurs européens utilisent le point-virgule, et ce qu'un élément vide fait à l'aller-retour — chaque cas exécuté et imprimé.
La ligne de code, et le moment exact où elle casse
Liste à retours à la ligne vers liste à virgules : c'est une jointure. L'inverse : un découpage. Les deux sont corrects exactement tant qu'aucun élément ne contient le délimiteur, et dès qu'un élément en contient, la conversion cesse d'être réversible sans le dire. Notre liste test de cinq éléments — Smith, John / Doe, Jane / O’Neill, « Bud » / une adresse sur deux lignes / plain — jointe par des virgules puis redécoupée a rendu huit éléments. Aucune exception, aucun avertissement, et trois des huit étaient des fragments de noms. Ce silence est tout le problème : un convertisseur de listes qui perd des données produit une liste plausible, pas une erreur.
La même expérience dans chacune de nos langues donne la même forme : trois entrées « nom, prénom » deviennent six après un aller-retour naïf, en anglais, français, espagnol, portugais, allemand et italien. Écrites via un producteur RFC 4180 et relues par un analyseur RFC 4180, toutes reviennent en trois éléments identiques à l'entrée. La règle n'est pas propre à une langue ; seules les données qui la font trébucher le sont.
Ce que dit vraiment la RFC 4180
La RFC 4180 est courte, d'octobre 2005, et — bon à savoir — Informational plutôt que norme. Ses sept règles : les enregistrements sont séparés par CRLF ; le dernier n'a pas besoin d'en avoir un ; une ligne d'en-tête facultative peut venir en premier ; les champs sont séparés par des virgules et les espaces font partie du champ ; les guillemets sont facultatifs, mais un champ non encadré ne peut pas contenir de guillemet double ; les champs contenant des sauts de ligne, des guillemets doubles ou des virgules doivent être encadrés de guillemets doubles ; et un guillemet double à l'intérieur d'un champ encadré s'échappe en le faisant précéder d'un autre guillemet double. C'est cette dernière règle qu'on remplace le plus souvent par une invention, en général une barre oblique inverse, qu'aucun lecteur CSV n'attend.
La grammaire ABNF de la section 2 est plus stricte que tout usage réel. TEXTDATA y est défini comme %x20-21 / %x23-2B / %x2D-7E, ce qui exclut la virgule à %x2C et le guillemet double à %x22 — ainsi que tout octet au-dessus de 127. Vérification faite : « plain » est conforme à TEXTDATA ; « café », « naïve », « Straße » et « ação » ne le sont pas. En pratique, le paramètre charset du type de média text/csv porte l'encodage et tout le monde écrit de l'UTF-8, mais cela rappelle que la RFC a codifié un désordre existant plutôt que conçu un format. Le document le dit lui-même en recommandant d'être conservateur dans ce qu'on produit et libéral dans ce qu'on accepte.
Un champ CSV peut contenir un saut de ligne
C'est la règle qui casse le plus d'importateurs, parce qu'elle contredit le modèle mental « un enregistrement par ligne ». Nous avons écrit un fichier de deux enregistrements dont le second champ est une adresse sur deux lignes : les octets sont id-1,"Line one CRLF Line two",ok CRLF id-2,flat,ok. Découpé sur le saut de ligne, il donne trois fragments ; analysé correctement, il donne deux enregistrements, dont le premier contient le saut de ligne intact. Tout code qui lit un CSV avec readLines est faux sur cette entrée, et l'entrée n'a rien d'exotique — adresses, descriptions produit et notes collées contiennent des sauts de ligne.
Les fins de ligne méritent leur paragraphe. La RFC impose CRLF entre enregistrements, et découper « a,b CRLF c,d CRLF » sur le seul saut de ligne rend ["a,b\r", "c,d\r", ""] — deux champs avec un retour chariot invisible collé et une queue vide. Ce \r parasite explique qu'une valeur soit jugée différente d'elle-même entre deux systèmes, et qu'une chaîne vide finale devienne une dernière ligne fantôme. Un analyseur qui consomme CRLF, LF et un CR isolé comme séparateurs d'enregistrements gère les trois familles de fichiers et rend les deux mêmes enregistrements pour chacune.
Pourquoi la moitié de l'Europe écrit le CSV avec des points-virgules
Demande à la plateforme à quoi ressemble un nombre dans chacune de nos six locales et la collision saute aux yeux. Le formatage de 1234567.5 donne 1,234,567.5 en en-US, 1 234 567,5 en fr-FR avec une espace fine insécable U+202F comme séparateur de groupes, 1.234.567,5 en es-ES, de-DE et it-IT, et 1 234 567,5 en pt-PT avec U+00A0. Cinq des six utilisent la virgule comme marque décimale. Une liste de prix délimitée par des virgules dans ces locales contient donc une virgule à l'intérieur d'un champ sur chaque ligne — et c'est exactement pourquoi leurs tableurs écrivent et attendent le point-virgule.
Le test rend cela concret. La ligne Chaise / 1 299,00 / 2 jointe par des virgules s'analyse en quatre champs — Chaise, 1 299, 00, 2 — parce que la marque décimale est une virgule en français. La ligne Chair / 1,299.00 / 2 s'analyse aussi en quatre pour la raison miroir : le séparateur de milliers est une virgule en anglais. Encadre le champ prix de guillemets et les deux redeviennent trois champs ; utilise un point-virgule et les deux font trois champs sans le moindre guillemet. Aucune approche n'est plus correcte : le point-virgule est celle qu'un tableur européen ouvre sans boîte de dialogue d'import, les virgules encadrées celle qu'une API acceptera.
Éléments vides, séparateurs finaux, et ce qu'aucun aller-retour ne peut récupérer
Découper la chaîne vide sur la virgule rend un élément vide, pas zéro. « a,b, » rend trois éléments, le dernier vide. « ,a » en rend deux, le premier vide. « a,,b » en rend trois, celui du milieu vide. Aucun n'est un bug ; tous découlent d'une seule définition — un séparateur sépare, donc n séparateurs signifient n+1 éléments. Ce que l'on veut d'ordinaire est la version filtrée, et filtrer est une décision qui supprime silencieusement un champ réellement vide.
Le cas irrécupérable est du côté de l'écriture. Joindre la liste vide et joindre une liste contenant une chaîne vide produisent tous deux la chaîne vide : les deux sont identiques sur le fil et aucun analyseur ne peut les distinguer. Notre propre analyseur minimal a aggravé les choses : en lisant la chaîne vide il rendait zéro enregistrement, ce qui est juste pour l'une des deux entrées et faux pour l'autre. Passer à un producteur qui encadre tous les champs corrige exactement cela : un élément vide devient les deux caractères "" et se relit en un seul élément vide, tandis que zéro élément reste la chaîne vide et se relit en rien. Si des éléments vides peuvent apparaître dans tes données, tout encadrer n'est pas une question de style.
JSON, tabulations, et choisir un délimiteur exprès
Un tableau JSON évite tout le débat en encadrant tout et en échappant le reste : notre liste de cinq éléments a survécu à l'aller-retour sans changement, l'adresse sur deux lignes étant stockée comme une seule chaîne contenant \n. C'est le format à choisir quand un programme est aux deux bouts. Son coût : tout consommateur doit être un analyseur JSON, et JSON a des types — une liste de codes postaux revient en nombres si quelqu'un les écrit sans guillemets, et 01234 revient en 1234 ou en erreur de syntaxe.
Les valeurs séparées par tabulations sont le format sans spécification, ce qui explique qu'elles marchent si souvent et échouent si discrètement. Notre liste adverse contenait une tabulation dans un élément : un délimiteur tabulation l'aurait coupé. Avant de choisir un délimiteur, regarde : sur cette liste, la virgule, le point-virgule et la tabulation apparaissaient tous à l'intérieur d'éléments, la barre verticale, U+001F et l'octet nul non. Un délimiteur dont on prouve l'absence ramène la conversion à la ligne de code qu'elle semblait être — et si aucun n'est sûr, encadre. Une dernière note pratique sans rapport avec l'analyse : un champ commençant par =, +, - ou @ est traité comme une formule par les tableurs, donc une liste de chaînes fournies par des utilisateurs doit voir ces champs neutralisés avant que quiconque ouvre le fichier.
| Format | Séparateur d'éléments | Séparateur dans un élément | Saut de ligne dans un élément | Éléments après aller-retour |
|---|---|---|---|---|
| Un élément par ligne | Saut de ligne | Sans problème — les virgules sont des caractères ordinaires | Impossible — il termine l'élément | 3 sur 3 |
| Jointure naïve par virgules | Virgule, sans guillemets | Casse — l'élément se coupe en deux | Casse — ressemble à un nouvel enregistrement | 6 sur 3 |
| CSV RFC 4180 | Virgule, champs encadrés si besoin | Encadrer le champ de guillemets | Légal dans un champ encadré | 3 sur 3 |
| CSV à point-virgule (tableurs européens) | Point-virgule | Même règle de guillemets, autre délimiteur | Légal dans un champ encadré | 3 sur 3 |
| Tableau JSON | Virgule entre chaînes encadrées | Aucun souci — toute chaîne est encadrée | Échappé en \n | 3 sur 3 |
| Séparé par tabulations | Tabulation | Sûr seulement si aucun élément ne contient de tabulation — le nôtre en contenait | Non défini par une norme | 3 sur 3 seulement avec de la chance |
Questions fréquentes
- Un champ CSV peut-il vraiment contenir un saut de ligne ?
- Oui, et la RFC 4180 le dit explicitement : un champ contenant des sauts de ligne doit être encadré de guillemets doubles, et l'ABNF autorise CR et LF dans un champ échappé. Nous avons écrit un fichier de deux enregistrements dont le second champ contenait une adresse sur deux lignes ; il occupe trois lignes physiques, donc compter les lignes donne 3 et analyser donne 2. Tout importateur bâti sur la lecture de lignes est faux sur ce fichier.
- Un fichier délimité par des points-virgules est-il encore du CSV ?
- Pas selon la RFC 4180, dont l'ABNF fixe le délimiteur à %x2C, la virgule. En pratique, c'est ce qu'écrivent les tableurs dans toutes les locales qui utilisent la virgule décimale — cinq des six nôtres. Garde le reste des règles : encadre les champs contenant le délimiteur, double les guillemets, sépare les enregistrements par CRLF. Nomme le fichier honnêtement et précise le délimiteur en le transmettant.
- Une chaîne vide est-elle un élément ou zéro ?
- Le découpage dit un : découper « » sur la virgule rend une liste d'un élément vide. La jointure ne peut pas trancher, car [] et [""] produisent tous deux la chaîne vide. La réponse est donc une convention à choisir et à consigner, pas une information contenue dans les données. Le seul moyen de conserver la distinction dans un aller-retour est d'encadrer chaque champ de guillemets, ce qui transforme un élément vide en deux guillemets et zéro élément en rien du tout.
- Comment échapper un guillemet double dans un champ ?
- En le doublant, à l'intérieur d'un champ encadré. L'élément say "hi" s'écrit "say ""hi""" — un guillemet ouvrant, le texte avec chaque guillemet intérieur écrit deux fois, un guillemet fermant. La barre oblique inverse ne fait rien : CSV n'a pas d'échappement par barre oblique. Notre liste adverse de onze éléments, qui contenait cette chaîne et une chaîne déjà encadrée, a fait l'aller-retour à l'identique sous cette règle.
- Quand faut-il plutôt utiliser un tableau JSON ?
- Dès que les deux bouts sont des programmes. JSON encadre chaque chaîne et échappe les caractères de contrôle : les éléments contenant virgules, guillemets et sauts de ligne n'exigent aucun traitement spécial — notre liste de cinq éléments a fait l'aller-retour sans changement, l'élément à deux lignes stocké avec \n dans une seule chaîne. Préfère le CSV quand un tableur ou une personne est à l'autre bout, et rappelle-toi que JSON a des types : les identifiants à zéro initial doivent rester des chaînes.
Articles qui pourraient t'intéresser
Tous les guides →Outils similaires
Sources
- IETF (RFC Editor) — RFC 4180 — Common Format and MIME Type for Comma-Separated Values (CSV) Files, October 2005, Informational
- IANA — Media type registration for text/csv, with the optional charset and header parameters
- W3C — Model for Tabular Data and Metadata on the Web — what a CSV file does and does not carry
- Ecma International — ECMA-404 — The JSON Data Interchange Syntax
- Unicode Consortium (CLDR) — Common Locale Data Repository — per-locale decimal and grouping separators
- OWASP — CSV Injection — why a field beginning with =, +, - or @ is a security concern in spreadsheets
Tu as repéré une erreur dans cet article ?