[ADD] csv: decode in four steps, choose the separator, cut records
Bytes decode in the order of § 10.1: UTF-8 mark, UTF-16 mark, a NUL in
the first 4 KiB without a mark refused with its remedy, then strict
UTF-8, then windows-1252. A mark its bytes contradict, and any decoded
NUL, are refused: decoded, they would give names no later check sees.
The separator is the candidate among ";", "," and tab that leaves no
quote open and gives one field count above one on the first twenty
records; a lone recognised header reads as one column. Records follow
RFC 4180 and are numbered as a spreadsheet numbers its rows.
Checked: 89 tests, 60 mutants killed; review found no behaviour defect.
--- FR ---
[ADD] csv : décodage en quatre temps, choix du séparateur, découpage
Les octets se décodent dans l'ordre du § 10.1 : marque UTF-8, marque
UTF-16, octet nul dans les 4 Kio sans marque refusé avec son remède,
UTF-8 strict, puis windows-1252. Une marque contredite par ses octets,
et tout nul décodé, sont refusés : décodés, ils donneraient des noms
qu'aucun contrôle ne verrait. Le séparateur parmi « ; », « , » et la
tabulation ne laisse aucun guillemet ouvert et donne un même nombre de
champs, plus d'un, sur vingt enregistrements ; un en-tête seul reconnu
se lit en une colonne. Découpage RFC 4180, rangs comme dans un tableur.
Vérifié : 89 épreuves, 60 mutants tués ; aucun défaut à la revue.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 07:05:05 -04:00
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
// Lecture d'un texte CSV déjà décodé (§ 10.1, § 10.2) : sa découpe en
|
|
|
|
|
// enregistrements et le choix de son séparateur. L'import d'un fichier et le
|
|
|
|
|
// collage en bloc passent par ces mêmes fonctions : le texte qu'elles reçoivent
|
|
|
|
|
// n'a plus d'origine, et un second analyseur « simplifié » divergerait du
|
|
|
|
|
// premier sur les accents et les guillemets.
|
|
|
|
|
|
|
|
|
|
import { ErreurCsv } from './erreurs.js';
|
|
|
|
|
|
|
|
|
|
const GUILLEMET = '"';
|
|
|
|
|
|
|
|
|
|
// Candidats du séparateur, dans l'ordre qui départage deux candidats à égalité
|
|
|
|
|
// de tout le reste.
|
|
|
|
|
const CANDIDATS = [';', ',', '\t'];
|
|
|
|
|
|
|
|
|
|
// Nombre d'enregistrements que le choix du séparateur lit.
|
|
|
|
|
const FENETRE = 20;
|
|
|
|
|
|
|
|
|
|
// Parcourt texte en un seul passage et rend ses enregistrements, avec l'état
|
|
|
|
|
// des guillemets à la fin de la lecture. La lecture s'arrête après limite
|
|
|
|
|
// enregistrements terminés ; guillemetOuvert est alors faux, la lecture
|
|
|
|
|
// s'arrêtant hors d'un champ cité.
|
|
|
|
|
//
|
|
|
|
|
// La découpe suit RFC 4180, avec ces précisions :
|
|
|
|
|
// - un guillemet ouvre un champ cité quand il en est le premier caractère ;
|
|
|
|
|
// ailleurs, c'est un caractère comme un autre ;
|
|
|
|
|
// - dans un champ cité, le séparateur et les fins de ligne font partie du
|
|
|
|
|
// texte, deux guillemets font un guillemet, un seul referme le champ ; ce qui
|
|
|
|
|
// suit le guillemet fermant, jusqu'au séparateur, reste dans le champ ;
|
|
|
|
|
// - CRLF, LF ou CR terminent un enregistrement, CRLF comptant pour une seule
|
|
|
|
|
// fin ;
|
|
|
|
|
// - une ligne vide est un enregistrement d'un seul champ vide ; la ligne vide
|
|
|
|
|
// qui suit la dernière fin de ligne ne fait pas d'enregistrement ;
|
|
|
|
|
// - un séparateur null ne sépare rien : chaque enregistrement est un champ ;
|
|
|
|
|
// - aucun blanc n'est retiré.
|
|
|
|
|
function analyser(texte, separateur, limite) {
|
|
|
|
|
const enregistrements = [];
|
|
|
|
|
let enregistrement = [];
|
|
|
|
|
let champ = '';
|
|
|
|
|
let cite = false;
|
|
|
|
|
let debutDeChamp = true;
|
|
|
|
|
let debutDeLigne = true;
|
|
|
|
|
for (let i = 0; i < texte.length; i += 1) {
|
|
|
|
|
const c = texte[i];
|
|
|
|
|
if (cite) {
|
|
|
|
|
if (c !== GUILLEMET) {
|
|
|
|
|
champ += c;
|
|
|
|
|
} else if (texte[i + 1] === GUILLEMET) {
|
|
|
|
|
champ += GUILLEMET;
|
|
|
|
|
i += 1;
|
|
|
|
|
} else {
|
|
|
|
|
cite = false;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (c === '\n' || c === '\r') {
|
|
|
|
|
if (c === '\r' && texte[i + 1] === '\n') i += 1;
|
|
|
|
|
enregistrement.push(champ);
|
|
|
|
|
enregistrements.push(enregistrement);
|
|
|
|
|
if (enregistrements.length >= limite) return { enregistrements, guillemetOuvert: false };
|
|
|
|
|
enregistrement = [];
|
|
|
|
|
champ = '';
|
|
|
|
|
debutDeChamp = true;
|
|
|
|
|
debutDeLigne = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
debutDeLigne = false;
|
|
|
|
|
if (c === separateur) {
|
|
|
|
|
enregistrement.push(champ);
|
|
|
|
|
champ = '';
|
|
|
|
|
debutDeChamp = true;
|
|
|
|
|
} else if (c === GUILLEMET && debutDeChamp) {
|
|
|
|
|
cite = true;
|
|
|
|
|
} else {
|
|
|
|
|
champ += c;
|
|
|
|
|
debutDeChamp = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!debutDeLigne) {
|
|
|
|
|
enregistrement.push(champ);
|
|
|
|
|
enregistrements.push(enregistrement);
|
|
|
|
|
}
|
|
|
|
|
return { enregistrements, guillemetOuvert: cite };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Découpe un texte en enregistrements de champs, selon RFC 4180 : champs cités,
|
|
|
|
|
* guillemets doublés, séparateur et fins de ligne dans un champ cité, fins de
|
|
|
|
|
* ligne CRLF, LF ou CR. Une ligne vide est un enregistrement d'un seul champ
|
|
|
|
|
* vide ; la ligne vide qui suit la dernière fin de ligne ne fait pas
|
|
|
|
|
* d'enregistrement. Aucun blanc n'est retiré des champs.
|
|
|
|
|
*
|
|
|
|
|
* Un enregistrement se désigne par son rang à partir de 1, l'en-tête compris :
|
|
|
|
|
* le numéro de ligne qu'un tableur lui donne. Un champ cité sur plusieurs
|
|
|
|
|
* lignes ne fait qu'un enregistrement ; la découpe ne rend aucun numéro de
|
|
|
|
|
* ligne du texte.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} texte
|
|
|
|
|
* @param {string|null} separateur un caractère, ou null pour un fichier à une
|
|
|
|
|
* colonne, dont chaque ligne est un seul champ
|
|
|
|
|
* @returns {{ enregistrements: string[][], guillemetOuvert: boolean }}
|
|
|
|
|
* guillemetOuvert est vrai quand le texte s'achève dans un champ cité : le
|
|
|
|
|
* dernier enregistrement porte alors le texte lu jusque-là.
|
|
|
|
|
*/
|
|
|
|
|
export function decouper(texte, separateur) {
|
|
|
|
|
return analyser(texte, separateur, Infinity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Vrai quand chaque champ de l'enregistrement est vide ou réduit à des blancs :
|
|
|
|
|
* une ligne vide, une ligne de séparateurs seuls, une ligne de blancs. Le choix
|
|
|
|
|
* du séparateur ne compte pas ces enregistrements.
|
|
|
|
|
*
|
|
|
|
|
* @param {string[]} enregistrement
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
export function enregistrementVide(enregistrement) {
|
|
|
|
|
return enregistrement.every((champ) => champ.trim() === '');
|
|
|
|
|
}
|
|
|
|
|
|
[ADD] csv: columns by header, closed exclu values, mandatory preview
Columns associate by header, never by position, insensitive to case,
accents and spaces, with the § 10.1 synonyms; two columns for one field
associate neither and ask. The preview imports nothing: it lists valid
rows, refused rows with their reason, each group of merged spellings
with its counts, duplicates never merged, and global refusals, an open
quote or a NUL included. A paste goes through the same parser, and the
separator rule lives once, in lecture.js, whose refusal names its line.
Checked: 185 CSV tests, eight spreadsheet-shaped fixtures; two reviews.
--- FR ---
[ADD] csv : colonnes par en-tête, valeurs d'exclu fermées, aperçu
Les colonnes s'associent par en-tête, jamais par position, sans égard à
la casse, aux accents ni aux espaces, avec les synonymes du § 10.1 ;
deux colonnes d'un même champ ne s'associent pas, et le logiciel
demande. L'aperçu n'importe rien : lignes valides, lignes refusées et
leur motif, chaque groupe d'orthographes fondues et ses comptes,
doublons jamais fondus, refus globaux, guillemet ouvert et nul compris.
Le collage passe par le même analyseur, et la règle du séparateur ne
s'écrit qu'une fois, dans lecture.js, dont le refus nomme sa ligne.
Vérifié : 185 épreuves CSV, huit données de tableur ; deux revues.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:28 -04:00
|
|
|
// Lecture d'un texte par un candidat dans la fenêtre du choix. L'en-tête est
|
|
|
|
|
// le premier enregistrement non vide parmi ceux que la fenêtre termine : un
|
|
|
|
|
// guillemet resté ouvert court jusqu'à la fin du texte, et l'enregistrement
|
|
|
|
|
// où il s'ouvre, le dernier lu, reste inachevé. Rend null quand l'en-tête
|
|
|
|
|
// manque ou n'a qu'un champ : la constance seule ne suffit pas, et le candidat
|
|
|
|
|
// n'a pas de défaut à nommer. Sinon, rend le nombre d'en-têtes reconnus et le
|
|
|
|
|
// premier défaut qui écarte le candidat, ou null : un enregistrement non vide
|
|
|
|
|
// dont le nombre de champs diffère de celui de l'en-tête, à son rang ; à
|
|
|
|
|
// défaut, le guillemet resté ouvert, au rang où il s'ouvre.
|
|
|
|
|
function lireAvec(texte, candidat, reconnaitre) {
|
|
|
|
|
const { enregistrements, guillemetOuvert } = analyser(texte, candidat, FENETRE);
|
|
|
|
|
const termines = guillemetOuvert ? enregistrements.slice(0, -1) : enregistrements;
|
|
|
|
|
const entete = termines.find((e) => !enregistrementVide(e));
|
|
|
|
|
if (entete === undefined || entete.length < 2) return null;
|
|
|
|
|
const ecart = termines.findIndex((e) => !enregistrementVide(e) && e.length !== entete.length);
|
|
|
|
|
let defaut = null;
|
|
|
|
|
if (ecart !== -1) defaut = { ligne: ecart + 1, cause: 'NOMBRE_DE_CHAMPS' };
|
|
|
|
|
else if (guillemetOuvert) defaut = { ligne: enregistrements.length, cause: 'GUILLEMET_OUVERT' };
|
|
|
|
|
return { separateur: candidat, reconnus: entete.filter((champ) => reconnaitre(champ)).length, defaut };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// La lecture qui l'emporte, de la meilleure jusqu'ici et d'une suivante dans
|
|
|
|
|
// l'ordre des candidats : celle qui reconnaît le plus d'en-têtes, la première
|
|
|
|
|
// à égalité.
|
|
|
|
|
const meilleure = (jusquIci, suivante) =>
|
|
|
|
|
jusquIci === null || suivante.reconnus > jusquIci.reconnus ? suivante : jusquIci;
|
|
|
|
|
|
[ADD] csv: decode in four steps, choose the separator, cut records
Bytes decode in the order of § 10.1: UTF-8 mark, UTF-16 mark, a NUL in
the first 4 KiB without a mark refused with its remedy, then strict
UTF-8, then windows-1252. A mark its bytes contradict, and any decoded
NUL, are refused: decoded, they would give names no later check sees.
The separator is the candidate among ";", "," and tab that leaves no
quote open and gives one field count above one on the first twenty
records; a lone recognised header reads as one column. Records follow
RFC 4180 and are numbered as a spreadsheet numbers its rows.
Checked: 89 tests, 60 mutants killed; review found no behaviour defect.
--- FR ---
[ADD] csv : décodage en quatre temps, choix du séparateur, découpage
Les octets se décodent dans l'ordre du § 10.1 : marque UTF-8, marque
UTF-16, octet nul dans les 4 Kio sans marque refusé avec son remède,
UTF-8 strict, puis windows-1252. Une marque contredite par ses octets,
et tout nul décodé, sont refusés : décodés, ils donneraient des noms
qu'aucun contrôle ne verrait. Le séparateur parmi « ; », « , » et la
tabulation ne laisse aucun guillemet ouvert et donne un même nombre de
champs, plus d'un, sur vingt enregistrements ; un en-tête seul reconnu
se lit en une colonne. Découpage RFC 4180, rangs comme dans un tableur.
Vérifié : 89 épreuves, 60 mutants tués ; aucun défaut à la revue.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 07:05:05 -04:00
|
|
|
/**
|
|
|
|
|
* Choisit le séparateur parmi « ; », « , » et la tabulation en lisant les vingt
|
|
|
|
|
* premiers enregistrements avec chacun. Un candidat convient quand la lecture
|
|
|
|
|
* s'achève sans guillemet ouvert et que les enregistrements non vides ont tous
|
|
|
|
|
* le même nombre de champs, supérieur à 1 : sur un fichier séparé par des
|
|
|
|
|
* virgules, « ; » donne lui aussi un nombre de champs constant, un seul, et la
|
|
|
|
|
* constance ne suffit pas.
|
|
|
|
|
*
|
|
|
|
|
* À égalité, le candidat dont le premier enregistrement non vide compte le plus
|
|
|
|
|
* d'en-têtes reconnus l'emporte ; à égalité encore, le premier dans l'ordre
|
|
|
|
|
* « ; », « , », tabulation.
|
|
|
|
|
*
|
|
|
|
|
* Quand aucun candidat ne convient et que la première ligne entière, ses blancs
|
|
|
|
|
* de bord retirés, est un en-tête reconnu, le fichier n'a qu'une colonne :
|
|
|
|
|
* separateur est null, et un nom qui porte une virgule reste entier. Sinon,
|
[ADD] csv: columns by header, closed exclu values, mandatory preview
Columns associate by header, never by position, insensitive to case,
accents and spaces, with the § 10.1 synonyms; two columns for one field
associate neither and ask. The preview imports nothing: it lists valid
rows, refused rows with their reason, each group of merged spellings
with its counts, duplicates never merged, and global refusals, an open
quote or a NUL included. A paste goes through the same parser, and the
separator rule lives once, in lecture.js, whose refusal names its line.
Checked: 185 CSV tests, eight spreadsheet-shaped fixtures; two reviews.
--- FR ---
[ADD] csv : colonnes par en-tête, valeurs d'exclu fermées, aperçu
Les colonnes s'associent par en-tête, jamais par position, sans égard à
la casse, aux accents ni aux espaces, avec les synonymes du § 10.1 ;
deux colonnes d'un même champ ne s'associent pas, et le logiciel
demande. L'aperçu n'importe rien : lignes valides, lignes refusées et
leur motif, chaque groupe d'orthographes fondues et ses comptes,
doublons jamais fondus, refus globaux, guillemet ouvert et nul compris.
Le collage passe par le même analyseur, et la règle du séparateur ne
s'écrit qu'une fois, dans lecture.js, dont le refus nomme sa ligne.
Vérifié : 185 épreuves CSV, huit données de tableur ; deux revues.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:28 -04:00
|
|
|
* ErreurCsv('SEPARATEUR_INTROUVABLE'), dont les détails nomment ce qui écarte
|
|
|
|
|
* le candidat que le même départage désigne parmi ceux dont l'en-tête a au
|
|
|
|
|
* moins deux champs : { separateur, ligne, cause }, ligne étant le rang de
|
|
|
|
|
* l'enregistrement fautif et cause NOMBRE_DE_CHAMPS — le premier
|
|
|
|
|
* enregistrement non vide dont le nombre de champs diffère de celui de
|
|
|
|
|
* l'en-tête — ou GUILLEMET_OUVERT — sans écart, le guillemet resté ouvert,
|
|
|
|
|
* au rang où il s'ouvre. Sans un tel candidat, les détails sont vides.
|
[ADD] csv: decode in four steps, choose the separator, cut records
Bytes decode in the order of § 10.1: UTF-8 mark, UTF-16 mark, a NUL in
the first 4 KiB without a mark refused with its remedy, then strict
UTF-8, then windows-1252. A mark its bytes contradict, and any decoded
NUL, are refused: decoded, they would give names no later check sees.
The separator is the candidate among ";", "," and tab that leaves no
quote open and gives one field count above one on the first twenty
records; a lone recognised header reads as one column. Records follow
RFC 4180 and are numbered as a spreadsheet numbers its rows.
Checked: 89 tests, 60 mutants killed; review found no behaviour defect.
--- FR ---
[ADD] csv : décodage en quatre temps, choix du séparateur, découpage
Les octets se décodent dans l'ordre du § 10.1 : marque UTF-8, marque
UTF-16, octet nul dans les 4 Kio sans marque refusé avec son remède,
UTF-8 strict, puis windows-1252. Une marque contredite par ses octets,
et tout nul décodé, sont refusés : décodés, ils donneraient des noms
qu'aucun contrôle ne verrait. Le séparateur parmi « ; », « , » et la
tabulation ne laisse aucun guillemet ouvert et donne un même nombre de
champs, plus d'un, sur vingt enregistrements ; un en-tête seul reconnu
se lit en une colonne. Découpage RFC 4180, rangs comme dans un tableur.
Vérifié : 89 épreuves, 60 mutants tués ; aucun défaut à la revue.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 07:05:05 -04:00
|
|
|
*
|
|
|
|
|
* @param {string} texte
|
|
|
|
|
* @param {(entete: string) => unknown} reconnaitre rend une valeur vraie pour
|
|
|
|
|
* un en-tête connu ; reçoit chaque champ du premier enregistrement non vide
|
|
|
|
|
* tel qu'il est écrit, ou la première ligne entière d'un fichier à une
|
|
|
|
|
* colonne, ses blancs de bord retirés
|
|
|
|
|
* @returns {{ separateur: string|null }}
|
[ADD] csv: columns by header, closed exclu values, mandatory preview
Columns associate by header, never by position, insensitive to case,
accents and spaces, with the § 10.1 synonyms; two columns for one field
associate neither and ask. The preview imports nothing: it lists valid
rows, refused rows with their reason, each group of merged spellings
with its counts, duplicates never merged, and global refusals, an open
quote or a NUL included. A paste goes through the same parser, and the
separator rule lives once, in lecture.js, whose refusal names its line.
Checked: 185 CSV tests, eight spreadsheet-shaped fixtures; two reviews.
--- FR ---
[ADD] csv : colonnes par en-tête, valeurs d'exclu fermées, aperçu
Les colonnes s'associent par en-tête, jamais par position, sans égard à
la casse, aux accents ni aux espaces, avec les synonymes du § 10.1 ;
deux colonnes d'un même champ ne s'associent pas, et le logiciel
demande. L'aperçu n'importe rien : lignes valides, lignes refusées et
leur motif, chaque groupe d'orthographes fondues et ses comptes,
doublons jamais fondus, refus globaux, guillemet ouvert et nul compris.
Le collage passe par le même analyseur, et la règle du séparateur ne
s'écrit qu'une fois, dans lecture.js, dont le refus nomme sa ligne.
Vérifié : 185 épreuves CSV, huit données de tableur ; deux revues.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:28 -04:00
|
|
|
* @throws {ErreurCsv} SEPARATEUR_INTROUVABLE, détails
|
|
|
|
|
* { separateur, ligne, cause } ou {}
|
[ADD] csv: decode in four steps, choose the separator, cut records
Bytes decode in the order of § 10.1: UTF-8 mark, UTF-16 mark, a NUL in
the first 4 KiB without a mark refused with its remedy, then strict
UTF-8, then windows-1252. A mark its bytes contradict, and any decoded
NUL, are refused: decoded, they would give names no later check sees.
The separator is the candidate among ";", "," and tab that leaves no
quote open and gives one field count above one on the first twenty
records; a lone recognised header reads as one column. Records follow
RFC 4180 and are numbered as a spreadsheet numbers its rows.
Checked: 89 tests, 60 mutants killed; review found no behaviour defect.
--- FR ---
[ADD] csv : décodage en quatre temps, choix du séparateur, découpage
Les octets se décodent dans l'ordre du § 10.1 : marque UTF-8, marque
UTF-16, octet nul dans les 4 Kio sans marque refusé avec son remède,
UTF-8 strict, puis windows-1252. Une marque contredite par ses octets,
et tout nul décodé, sont refusés : décodés, ils donneraient des noms
qu'aucun contrôle ne verrait. Le séparateur parmi « ; », « , » et la
tabulation ne laisse aucun guillemet ouvert et donne un même nombre de
champs, plus d'un, sur vingt enregistrements ; un en-tête seul reconnu
se lit en une colonne. Découpage RFC 4180, rangs comme dans un tableur.
Vérifié : 89 épreuves, 60 mutants tués ; aucun défaut à la revue.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 07:05:05 -04:00
|
|
|
*/
|
|
|
|
|
export function choisirSeparateur(texte, reconnaitre) {
|
|
|
|
|
let retenu = null;
|
[ADD] csv: columns by header, closed exclu values, mandatory preview
Columns associate by header, never by position, insensitive to case,
accents and spaces, with the § 10.1 synonyms; two columns for one field
associate neither and ask. The preview imports nothing: it lists valid
rows, refused rows with their reason, each group of merged spellings
with its counts, duplicates never merged, and global refusals, an open
quote or a NUL included. A paste goes through the same parser, and the
separator rule lives once, in lecture.js, whose refusal names its line.
Checked: 185 CSV tests, eight spreadsheet-shaped fixtures; two reviews.
--- FR ---
[ADD] csv : colonnes par en-tête, valeurs d'exclu fermées, aperçu
Les colonnes s'associent par en-tête, jamais par position, sans égard à
la casse, aux accents ni aux espaces, avec les synonymes du § 10.1 ;
deux colonnes d'un même champ ne s'associent pas, et le logiciel
demande. L'aperçu n'importe rien : lignes valides, lignes refusées et
leur motif, chaque groupe d'orthographes fondues et ses comptes,
doublons jamais fondus, refus globaux, guillemet ouvert et nul compris.
Le collage passe par le même analyseur, et la règle du séparateur ne
s'écrit qu'une fois, dans lecture.js, dont le refus nomme sa ligne.
Vérifié : 185 épreuves CSV, huit données de tableur ; deux revues.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:28 -04:00
|
|
|
let ecarte = null;
|
[ADD] csv: decode in four steps, choose the separator, cut records
Bytes decode in the order of § 10.1: UTF-8 mark, UTF-16 mark, a NUL in
the first 4 KiB without a mark refused with its remedy, then strict
UTF-8, then windows-1252. A mark its bytes contradict, and any decoded
NUL, are refused: decoded, they would give names no later check sees.
The separator is the candidate among ";", "," and tab that leaves no
quote open and gives one field count above one on the first twenty
records; a lone recognised header reads as one column. Records follow
RFC 4180 and are numbered as a spreadsheet numbers its rows.
Checked: 89 tests, 60 mutants killed; review found no behaviour defect.
--- FR ---
[ADD] csv : décodage en quatre temps, choix du séparateur, découpage
Les octets se décodent dans l'ordre du § 10.1 : marque UTF-8, marque
UTF-16, octet nul dans les 4 Kio sans marque refusé avec son remède,
UTF-8 strict, puis windows-1252. Une marque contredite par ses octets,
et tout nul décodé, sont refusés : décodés, ils donneraient des noms
qu'aucun contrôle ne verrait. Le séparateur parmi « ; », « , » et la
tabulation ne laisse aucun guillemet ouvert et donne un même nombre de
champs, plus d'un, sur vingt enregistrements ; un en-tête seul reconnu
se lit en une colonne. Découpage RFC 4180, rangs comme dans un tableur.
Vérifié : 89 épreuves, 60 mutants tués ; aucun défaut à la revue.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 07:05:05 -04:00
|
|
|
for (const candidat of CANDIDATS) {
|
[ADD] csv: columns by header, closed exclu values, mandatory preview
Columns associate by header, never by position, insensitive to case,
accents and spaces, with the § 10.1 synonyms; two columns for one field
associate neither and ask. The preview imports nothing: it lists valid
rows, refused rows with their reason, each group of merged spellings
with its counts, duplicates never merged, and global refusals, an open
quote or a NUL included. A paste goes through the same parser, and the
separator rule lives once, in lecture.js, whose refusal names its line.
Checked: 185 CSV tests, eight spreadsheet-shaped fixtures; two reviews.
--- FR ---
[ADD] csv : colonnes par en-tête, valeurs d'exclu fermées, aperçu
Les colonnes s'associent par en-tête, jamais par position, sans égard à
la casse, aux accents ni aux espaces, avec les synonymes du § 10.1 ;
deux colonnes d'un même champ ne s'associent pas, et le logiciel
demande. L'aperçu n'importe rien : lignes valides, lignes refusées et
leur motif, chaque groupe d'orthographes fondues et ses comptes,
doublons jamais fondus, refus globaux, guillemet ouvert et nul compris.
Le collage passe par le même analyseur, et la règle du séparateur ne
s'écrit qu'une fois, dans lecture.js, dont le refus nomme sa ligne.
Vérifié : 185 épreuves CSV, huit données de tableur ; deux revues.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:28 -04:00
|
|
|
const lecture = lireAvec(texte, candidat, reconnaitre);
|
|
|
|
|
if (lecture === null) continue;
|
|
|
|
|
if (lecture.defaut === null) retenu = meilleure(retenu, lecture);
|
|
|
|
|
else ecarte = meilleure(ecarte, lecture);
|
[ADD] csv: decode in four steps, choose the separator, cut records
Bytes decode in the order of § 10.1: UTF-8 mark, UTF-16 mark, a NUL in
the first 4 KiB without a mark refused with its remedy, then strict
UTF-8, then windows-1252. A mark its bytes contradict, and any decoded
NUL, are refused: decoded, they would give names no later check sees.
The separator is the candidate among ";", "," and tab that leaves no
quote open and gives one field count above one on the first twenty
records; a lone recognised header reads as one column. Records follow
RFC 4180 and are numbered as a spreadsheet numbers its rows.
Checked: 89 tests, 60 mutants killed; review found no behaviour defect.
--- FR ---
[ADD] csv : décodage en quatre temps, choix du séparateur, découpage
Les octets se décodent dans l'ordre du § 10.1 : marque UTF-8, marque
UTF-16, octet nul dans les 4 Kio sans marque refusé avec son remède,
UTF-8 strict, puis windows-1252. Une marque contredite par ses octets,
et tout nul décodé, sont refusés : décodés, ils donneraient des noms
qu'aucun contrôle ne verrait. Le séparateur parmi « ; », « , » et la
tabulation ne laisse aucun guillemet ouvert et donne un même nombre de
champs, plus d'un, sur vingt enregistrements ; un en-tête seul reconnu
se lit en une colonne. Découpage RFC 4180, rangs comme dans un tableur.
Vérifié : 89 épreuves, 60 mutants tués ; aucun défaut à la revue.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 07:05:05 -04:00
|
|
|
}
|
|
|
|
|
if (retenu !== null) return { separateur: retenu.separateur };
|
|
|
|
|
|
|
|
|
|
const premiere = analyser(texte, null, FENETRE).enregistrements.find((e) => !enregistrementVide(e));
|
|
|
|
|
if (premiere !== undefined && reconnaitre(premiere[0].trim())) return { separateur: null };
|
[ADD] csv: columns by header, closed exclu values, mandatory preview
Columns associate by header, never by position, insensitive to case,
accents and spaces, with the § 10.1 synonyms; two columns for one field
associate neither and ask. The preview imports nothing: it lists valid
rows, refused rows with their reason, each group of merged spellings
with its counts, duplicates never merged, and global refusals, an open
quote or a NUL included. A paste goes through the same parser, and the
separator rule lives once, in lecture.js, whose refusal names its line.
Checked: 185 CSV tests, eight spreadsheet-shaped fixtures; two reviews.
--- FR ---
[ADD] csv : colonnes par en-tête, valeurs d'exclu fermées, aperçu
Les colonnes s'associent par en-tête, jamais par position, sans égard à
la casse, aux accents ni aux espaces, avec les synonymes du § 10.1 ;
deux colonnes d'un même champ ne s'associent pas, et le logiciel
demande. L'aperçu n'importe rien : lignes valides, lignes refusées et
leur motif, chaque groupe d'orthographes fondues et ses comptes,
doublons jamais fondus, refus globaux, guillemet ouvert et nul compris.
Le collage passe par le même analyseur, et la règle du séparateur ne
s'écrit qu'une fois, dans lecture.js, dont le refus nomme sa ligne.
Vérifié : 185 épreuves CSV, huit données de tableur ; deux revues.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:28 -04:00
|
|
|
if (ecarte === null) throw new ErreurCsv('SEPARATEUR_INTROUVABLE');
|
|
|
|
|
throw new ErreurCsv('SEPARATEUR_INTROUVABLE', { separateur: ecarte.separateur, ...ecarte.defaut });
|
[ADD] csv: decode in four steps, choose the separator, cut records
Bytes decode in the order of § 10.1: UTF-8 mark, UTF-16 mark, a NUL in
the first 4 KiB without a mark refused with its remedy, then strict
UTF-8, then windows-1252. A mark its bytes contradict, and any decoded
NUL, are refused: decoded, they would give names no later check sees.
The separator is the candidate among ";", "," and tab that leaves no
quote open and gives one field count above one on the first twenty
records; a lone recognised header reads as one column. Records follow
RFC 4180 and are numbered as a spreadsheet numbers its rows.
Checked: 89 tests, 60 mutants killed; review found no behaviour defect.
--- FR ---
[ADD] csv : décodage en quatre temps, choix du séparateur, découpage
Les octets se décodent dans l'ordre du § 10.1 : marque UTF-8, marque
UTF-16, octet nul dans les 4 Kio sans marque refusé avec son remède,
UTF-8 strict, puis windows-1252. Une marque contredite par ses octets,
et tout nul décodé, sont refusés : décodés, ils donneraient des noms
qu'aucun contrôle ne verrait. Le séparateur parmi « ; », « , » et la
tabulation ne laisse aucun guillemet ouvert et donne un même nombre de
champs, plus d'un, sur vingt enregistrements ; un en-tête seul reconnu
se lit en une colonne. Découpage RFC 4180, rangs comme dans un tableur.
Vérifié : 89 épreuves, 60 mutants tués ; aucun défaut à la revue.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 07:05:05 -04:00
|
|
|
}
|