support lecture et écriture CSV
This commit is contained in:
parent
6a837fbcb3
commit
e3e3fc91f4
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\csv;
|
||||||
|
|
||||||
|
use nur\A;
|
||||||
|
use nur\ref\ref_csv;
|
||||||
|
|
||||||
|
class csv_flavours {
|
||||||
|
const MAP = [
|
||||||
|
"oo" => ref_csv::OO_FLAVOUR,
|
||||||
|
"ooffice" => ref_csv::OO_FLAVOUR,
|
||||||
|
ref_csv::OO_NAME => ref_csv::OO_FLAVOUR,
|
||||||
|
"xl" => ref_csv::XL_FLAVOUR,
|
||||||
|
"excel" => ref_csv::XL_FLAVOUR,
|
||||||
|
ref_csv::XL_NAME => ref_csv::XL_FLAVOUR,
|
||||||
|
];
|
||||||
|
|
||||||
|
const ENCODINGS = [
|
||||||
|
ref_csv::OO_FLAVOUR => ref_csv::OO_ENCODING,
|
||||||
|
ref_csv::XL_FLAVOUR => ref_csv::XL_ENCODING,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final function verifix(string $flavour): string {
|
||||||
|
$lflavour = strtolower($flavour);
|
||||||
|
if (array_key_exists($lflavour, self::MAP)) {
|
||||||
|
$flavour = self::MAP[$lflavour];
|
||||||
|
}
|
||||||
|
if (strlen($flavour) < 1) $flavour .= ",";
|
||||||
|
if (strlen($flavour) < 2) $flavour .= "\"";
|
||||||
|
if (strlen($flavour) < 3) $flavour .= "\\";
|
||||||
|
return $flavour;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function get_name(string $flavour): string {
|
||||||
|
if ($flavour == ref_csv::OO_FLAVOUR) return ref_csv::OO_NAME;
|
||||||
|
elseif ($flavour == ref_csv::XL_FLAVOUR) return ref_csv::XL_NAME;
|
||||||
|
else return $flavour;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function get_params(string $flavour): array {
|
||||||
|
return [$flavour[0], $flavour[1], $flavour[2]];
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function get_encoding(string $flavour): ?string {
|
||||||
|
return A::get(self::ENCODINGS, $flavour);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,11 @@
|
||||||
namespace nur\sery\os\file;
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
use nur\sery\NoMoreDataException;
|
use nur\sery\NoMoreDataException;
|
||||||
|
use nur\sery\os\csv\csv_flavours;
|
||||||
use nur\sery\os\EOFException;
|
use nur\sery\os\EOFException;
|
||||||
use nur\sery\os\IOException;
|
use nur\sery\os\IOException;
|
||||||
use nur\sery\php\iter\AbstractIterator;
|
use nur\sery\php\iter\AbstractIterator;
|
||||||
|
use nur\sery\ref\os\csv\ref_csv;
|
||||||
use nur\sery\str;
|
use nur\sery\str;
|
||||||
use nur\sery\ValueException;
|
use nur\sery\ValueException;
|
||||||
|
|
||||||
|
@ -139,6 +141,40 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
||||||
if ($closeReader) $this->close();
|
if ($closeReader) $this->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CSV_FLAVOUR = ref_csv::OO_FLAVOUR;
|
||||||
|
|
||||||
|
/** @var array paramètres pour la lecture et l'écriture de flux au format CSV */
|
||||||
|
protected $csvFlavour;
|
||||||
|
|
||||||
|
function setCsvFlavour(string $flavour): void {
|
||||||
|
$this->csvFlavour = csv_flavours::verifix($flavour);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCsvParams($fd): array {
|
||||||
|
$flavour = $this->csvFlavour;
|
||||||
|
if ($flavour === null) {
|
||||||
|
if ($fd === null) {
|
||||||
|
# utiliser la valeur par défaut
|
||||||
|
$flavour = static::DEFAULT_CSV_FLAVOUR;
|
||||||
|
} else {
|
||||||
|
# il faut déterminer le type de fichier CSV en lisant la première ligne
|
||||||
|
$pos = IOException::ensure_valid(ftell($fd));
|
||||||
|
$line = IOException::ensure_valid(fgets($fd));
|
||||||
|
$line = strpbrk($line, ",;\t");
|
||||||
|
if ($line === false) {
|
||||||
|
# aucun séparateur trouvé, prender la valeur par défaut
|
||||||
|
$flavour = static::DEFAULT_CSV_FLAVOUR;
|
||||||
|
} else {
|
||||||
|
$flavour = substr($line, 0, 1);
|
||||||
|
$flavour = csv_flavours::verifix($flavour);
|
||||||
|
}
|
||||||
|
IOException::ensure_valid(fseek($fd, $pos), true, -1);
|
||||||
|
}
|
||||||
|
$this->csvFlavour = $flavour;
|
||||||
|
}
|
||||||
|
return csv_flavours::get_params($flavour);
|
||||||
|
}
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# Reader
|
# Reader
|
||||||
|
|
||||||
|
@ -168,6 +204,18 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
||||||
return IOException::ensure_valid(fpassthru($fd), $this->throwOnError);
|
return IOException::ensure_valid(fpassthru($fd), $this->throwOnError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retourner la prochaine ligne au format CSV ou null si le fichier est arrivé
|
||||||
|
* à sa fin
|
||||||
|
*/
|
||||||
|
function fgetcsv(): ?array {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
$params = $this->getCsvParams($fd);
|
||||||
|
$row = fgetcsv($fd, 0, $params[0], $params[1], $params[2]);
|
||||||
|
if ($row === false && feof($fd)) return null;
|
||||||
|
return IOException::ensure_valid($row, $this->throwOnError);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* lire la prochaine ligne. la ligne est retournée *sans* le caractère de fin
|
* lire la prochaine ligne. la ligne est retournée *sans* le caractère de fin
|
||||||
* de ligne [\r]\n
|
* de ligne [\r]\n
|
||||||
|
@ -267,6 +315,12 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
||||||
return IOException::ensure_valid($r, $this->throwOnError);
|
return IOException::ensure_valid($r, $this->throwOnError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fputcsv(array $row): void {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
$params = $this->getCsvParams($fd);
|
||||||
|
IOException::ensure_valid(fputcsv($fd, $row, $params[0], $params[1], $params[2]));
|
||||||
|
}
|
||||||
|
|
||||||
/** @throws IOException */
|
/** @throws IOException */
|
||||||
function fflush(): self {
|
function fflush(): self {
|
||||||
$fd = $this->getResource();
|
$fd = $this->getResource();
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\ref\os\csv;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ref_csv: références des valeurs normalisées pour les fichiers CSV à
|
||||||
|
* destination de Microsoft Excel et de LibreOffice Calc
|
||||||
|
*/
|
||||||
|
class ref_csv {
|
||||||
|
const UTF8 = "utf-8";
|
||||||
|
const CP1252 = "cp1252";
|
||||||
|
const LATIN1 = "iso-8859-1";
|
||||||
|
|
||||||
|
const OO_NAME = "oocalc";
|
||||||
|
const OO_FLAVOUR = ",\"\\";
|
||||||
|
const OO_ENCODING = self::UTF8;
|
||||||
|
|
||||||
|
const XL_NAME = "msexcel";
|
||||||
|
const XL_FLAVOUR = ";\"\\";
|
||||||
|
const XL_ENCODING = self::CP1252;
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class FileReaderTest extends TestCase {
|
||||||
|
function testIgnoreBom() {
|
||||||
|
# la lecture avec et sans BOM doit être identique
|
||||||
|
## sans BOM
|
||||||
|
$reader = new FileReader(__DIR__.'/impl/sans_bom.txt');
|
||||||
|
self::assertSame("0123456789", $reader->fread(10));
|
||||||
|
self::assertSame(10, $reader->ftell());
|
||||||
|
$reader->seek(30);
|
||||||
|
self::assertSame("abcdefghij", $reader->fread(10));
|
||||||
|
self::assertSame(40, $reader->ftell());
|
||||||
|
$reader->seek(10);
|
||||||
|
self::assertSame("ABCDEFGHIJ", $reader->fread(10));
|
||||||
|
self::assertSame(20, $reader->ftell());
|
||||||
|
$reader->seek(40);
|
||||||
|
self::assertSame("0123456789\n", $reader->getContents());
|
||||||
|
$reader->close();
|
||||||
|
## avec BOM
|
||||||
|
$reader = new FileReader(__DIR__.'/impl/avec_bom.txt');
|
||||||
|
self::assertSame("0123456789", $reader->fread(10));
|
||||||
|
self::assertSame(10, $reader->ftell());
|
||||||
|
$reader->seek(30);
|
||||||
|
self::assertSame("abcdefghij", $reader->fread(10));
|
||||||
|
self::assertSame(40, $reader->ftell());
|
||||||
|
$reader->seek(10);
|
||||||
|
self::assertSame("ABCDEFGHIJ", $reader->fread(10));
|
||||||
|
self::assertSame(20, $reader->ftell());
|
||||||
|
$reader->seek(40);
|
||||||
|
self::assertSame("0123456789\n", $reader->getContents());
|
||||||
|
$reader->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCsvAutoParams() {
|
||||||
|
$reader = new FileReader(__DIR__.'/impl/msexcel.csv');
|
||||||
|
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||||
|
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||||
|
self::assertNull($reader->fgetcsv());
|
||||||
|
$reader->close();
|
||||||
|
|
||||||
|
$reader = new FileReader(__DIR__.'/impl/ooffice.csv');
|
||||||
|
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||||
|
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||||
|
self::assertNull($reader->fgetcsv());
|
||||||
|
$reader->close();
|
||||||
|
|
||||||
|
$reader = new FileReader(__DIR__.'/impl/weird.tsv');
|
||||||
|
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||||
|
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||||
|
self::assertNull($reader->fgetcsv());
|
||||||
|
$reader->close();
|
||||||
|
|
||||||
|
$reader = new FileReader(__DIR__.'/impl/avec_bom.csv');
|
||||||
|
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||||
|
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||||
|
self::assertNull($reader->fgetcsv());
|
||||||
|
$reader->close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
nom,prenom,age
|
||||||
|
clain,jephte,50
|
|
|
@ -0,0 +1 @@
|
||||||
|
0123456789ABCDEFGHIJ0123456789abcdefghij0123456789
|
|
@ -0,0 +1,2 @@
|
||||||
|
nom;prenom;age
|
||||||
|
clain;jephte;50
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
nom,prenom,age
|
||||||
|
clain,jephte,50
|
|
|
@ -0,0 +1 @@
|
||||||
|
0123456789ABCDEFGHIJ0123456789abcdefghij0123456789
|
|
@ -0,0 +1,2 @@
|
||||||
|
nom prenom age
|
||||||
|
clain jephte 50
|
|
Loading…
Reference in New Issue