Aller au contenu
OneKitly

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

Daniel OkonkwoDéveloppeur front-end et rédacteur Tech chez OneKitly

Performance web · Formats de fichiers

Vérifié à partir de 6 sources

Voir le profil
En bref

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.

La même liste de trois éléments à travers cinq formats, exécutions dans Node 26.3.0. Éléments : Dupont, Marie / Martin, Jean / D’Arcy, Léa. Seuls les formats munis d'une règle de guillemets ou d'échappement survivent à un élément contenant le délimiteur.
FormatSéparateur d'élémentsSéparateur dans un élémentSaut de ligne dans un élémentÉléments après aller-retour
Un élément par ligneSaut de ligneSans problème — les virgules sont des caractères ordinairesImpossible — il termine l'élément3 sur 3
Jointure naïve par virgulesVirgule, sans guillemetsCasse — l'élément se coupe en deuxCasse — ressemble à un nouvel enregistrement6 sur 3
CSV RFC 4180Virgule, champs encadrés si besoinEncadrer le champ de guillemetsLégal dans un champ encadré3 sur 3
CSV à point-virgule (tableurs européens)Point-virguleMême règle de guillemets, autre délimiteurLégal dans un champ encadré3 sur 3
Tableau JSONVirgule entre chaînes encadréesAucun souci — toute chaîne est encadréeÉchappé en \n3 sur 3
Séparé par tabulationsTabulationSûr seulement si aucun élément ne contient de tabulation — le nôtre en contenaitNon défini par une norme3 sur 3 seulement avec de la chance
Convertisseur de format de listeConvertis une liste entre puces, numéros et lignes simples.Essayer l'outil

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
ExplicationOù une ligne peut se couper : l'algorithme Unicode derrière chaque paragraphe justifié« Couper aux espaces » échoue dans la plupart des systèmes d'écriture. UAX #14 donne à chaque caractère une classe de coupure ; nous avons cherché les nôtres dans Unicode 17.0.0 et exécuté une implémentation conforme sur espaces insécables, traits d'union conditionnels, espaces de largeur nulle, URL, japonais et thaï.ExplicationFréquence des mots et loi de Zipf : nous avons compté six livres en six langues et ajusté la penteLe mot de rang n apparaît environ 1/n fois moins que le premier. Nous avons compté six livres du domaine public, imprimé rang × fréquence, ajusté log fréquence contre log rang, et obtenu des pentes entre -1,02 et -1,08 dans les six langues — et les deux endroits où la loi casse.GuideLes listes de tâches Markdown et ce qui s'affiche vraiment oùLes listes de tâches ne sont pas dans CommonMark. C'est une extension de GitHub Flavored Markdown, d'où des cases à cocher ici et des crochets littéraux là. La règle exacte du marqueur, l'effet de l'imbrication, et un tableau de ce qui est CommonMark, GFM ou ni l'un ni l'autre — vérifié sur les deux spécifications et quatre moteurs de rendu.TutorielFiltrer des lignes selon un motif, sans ligne de commandeC'est grep pour ceux qui n'utilisent pas grep, avec une différence de taille : la recherche est une sous-chaîne littérale, si bien qu'une vraie expression régulière renvoie une zone vide sans message d'erreur. Chaque affirmation a été vérifiée en exécutant l'outil.GuideFormater les nombres pour six langues : séparateurs, monnaie et le retour à la valeur1 234,56 et 1,234.56 sont le même nombre, et les confondre change la valeur que lit un lecteur. Nous avons lancé Intl.NumberFormat pour les six locales du site et imprimé chaque séparateur — dont l'invisible qu'emploie le français — puis mesuré pourquoi parseFloat ne peut rien défaire.ExplicationLes emoji sont plus difficiles qu'ils n'en ont l'air : pourquoi « il suffit de les retirer » n'a pas de réponse en une ligneUn emoji visible peut valoir un point de code ou quatorze unités UTF-16. Nous avons lancé trois expressions régulières répandues sur une vraie phrase : chacune a cassé différemment, l'une a supprimé les chiffres. Voici pourquoi, quelle propriété Unicode répond à quelle question, et la règle de groupes de graphèmes qui marche vraiment.

Outils similaires

Sources

Tu as repéré une erreur dans cet article ?