Browse Source

Ajout d'un exporteur xls qui permet un export plus etendu que le TSV limite : support des champs texte multilignes, des champs date et nombre

+ on modifie la signature de la callback CSV pour unifier avec l'export XLS en retirant le delim des arguments passes
v3 v3.7.0
Cerdic 3 months ago
parent
commit
d0c4b9022f
  1. 2
      inc/exporter_csv.php
  2. 133
      inc/exporter_xls.php
  3. 12
      lib/php-export-data/.gitignore
  4. 21
      lib/php-export-data/LICENSE
  5. 51
      lib/php-export-data/README.md
  6. 35
      lib/php-export-data/composer.json
  7. 97
      lib/php-export-data/src/ExportData.php
  8. 24
      lib/php-export-data/src/ExportDataCSV.php
  9. 182
      lib/php-export-data/src/ExportDataExcel.php
  10. 24
      lib/php-export-data/src/ExportDataTSV.php
  11. 2
      paquet.xml

2
inc/exporter_csv.php

@ -70,7 +70,7 @@ function exporter_csv_champ_no_lf($champ) {
*/
function exporter_csv_ligne_numerotee($nb, $ligne, $delim = ',', $importer_charset = null, $callback=null, $fonction_exporter_champ=null) {
if ($callback) {
$ligne = call_user_func($callback, $nb, $ligne, $delim, $importer_charset);
$ligne = call_user_func($callback, $nb, $ligne, $importer_charset);
}
if (!$fonction_exporter_champ or !function_exists($fonction_exporter_champ)) {
$fonction_exporter_champ = 'exporter_csv_champ';

133
inc/exporter_xls.php

@ -0,0 +1,133 @@
<?php
/**
* Plugin Spip-Bonux
* Le plugin qui lave plus SPIP que SPIP
* (c) 2008 Mathieu Marcillaud, Cedric Morin, Tetue
* Licence GPL
*
* Fonctions d'export d'une requete sql ou d'un tableau
* au format CSV
* Merge du plugin csv_import et spip-surcharges
*
*/
use Export\ExportDataExcel;
if (!defined('_ECRIRE_INC_VERSION')) {
return;
}
include_spip('inc/charsets');
include_spip('inc/filtres');
include_spip('inc/texte');
/**
* Preparer une ligne avant export XLS : charset si besoin + callback
*
* @param int $nb
* @param array $ligne
* @param string|null $importer_charset
* Si défini exporte dans le charset indiqué
* @param callable $callback
* @return string
*/
function exporter_xls_preparer_ligne_numerotee($nb, $ligne, $importer_charset = null, $callback=null) {
if ($callback) {
$ligne = call_user_func($callback, $nb, $ligne, $importer_charset);
}
foreach ($ligne as $k => $v) {
$ligne[$k] = str_replace('’', '\'', $ligne[$k]);
$ligne[$k] = rtrim($ligne[$k]);
if ($importer_charset and !is_numeric($v)) {
$ligne[$k] = unicode2charset(html2unicode(charset2unicode($ligne[$k])), $importer_charset);
}
}
return $ligne;
}
/**
* Exporte une ressource sous forme de fichier CSV
*
* La ressource peut etre un tableau ou une resource SQL issue d'une requete
* L'extension est choisie en fonction du delimiteur :
* - si on utilise ',' c'est un vrai csv avec extension csv
* - si on utilise ';' ou tabulation c'est pour E*cel, et on exporte en iso-truc, avec une extension .xls
*
* @uses exporter_xls_ligne()
*
* @param string $titre
* titre utilise pour nommer le fichier
* @param array|resource $resource
* @param array $options
* array $entetes : tableau d'en-tetes pour nommer les colonnes (genere la premiere ligne)
* bool $envoyer : pour envoyer le fichier exporte (permet le telechargement)
* string $charset : charset de l'export si different de celui du site
* callable callback : fonction callback a appeler sur chaque ligne pour mettre en forme/completer les donnees
* @return string
*/
function inc_exporter_xls_dist($titre, $resource, $options = []) {
include_spip('lib/php-export-data/src/ExportData');
include_spip('lib/php-export-data/src/ExportDataExcel');
$default_options = [
'entetes' => null,
'envoyer' => true,
'charset' => null,
'callback' => null,
];
$options = array_merge($default_options, $options);
$filename = preg_replace(',[^-_\w]+,', '_', translitteration(textebrut(typo($titre))));
$extension = 'xls';
$charset = $GLOBALS['meta']['charset'];
// mais si une option charset est explicite, elle a la priorite
if (!empty($options['charset'])) {
$charset = $options['charset'];
}
$importer_charset = (($charset === $GLOBALS['meta']['charset']) ? null : $charset);
$filename = "$filename.$extension";
$fichier = sous_repertoire(_DIR_CACHE, 'export') . $filename;
if ($options['envoyer']) {
// Vider tous les tampons
$level = @ob_get_level();
while ($level--) {
@ob_end_flush();
}
}
$exporter = new ExportDataExcel($options['envoyer'] ? 'browser' : 'file', $fichier);
$exporter->encoding = $charset;
$exporter->initialize(); // start export
$nb = 0;
if (!empty($options['entetes']) and is_array($options['entetes'])) {
$ligne = exporter_xls_preparer_ligne_numerotee($nb, $options['entetes'], $importer_charset, $options['callback']);
$exporter->addRow($ligne);
}
// les donnees commencent toujours a la ligne 1, qu'il y ait ou non des entetes
$nb++;
while ($row = is_array($resource) ? array_shift($resource) : sql_fetch($resource)) {
$ligne = exporter_xls_preparer_ligne_numerotee($nb, $row, $importer_charset, $options['callback']);
$exporter->addRow($ligne);
$nb++;
}
$exporter->finalize(); // writes the footer, flushes remaining data to browser.
if ($options['envoyer']) {
// si on a envoye inline, c'est deja tout bon
exit;
}
return $fichier;
}

12
lib/php-export-data/.gitignore

@ -0,0 +1,12 @@
nbproject/
nbproject/project.properties
tmp/
vendor/
composer.lock
/composer.lock
/composer.phar
/.idea
build/coverage
build/logs
cache.properties
/bin

21
lib/php-export-data/LICENSE

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Eli Dickinson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

51
lib/php-export-data/README.md

@ -0,0 +1,51 @@
# php-export-data
-- by Eli Dickinson
Improved version by Nursit https://github.com/nursit/php-export-data
* forked from https://github.com/diego3/php-export-data
* forked from http://github.com/elidickinson/php-export-data
Released under the permissive MIT License: http://www.opensource.org/licenses/mit-license.php
A simple library for exporting tabular data to Excel-friendly XML, CSV, or TSV. It supports streaming exported data to a file or directly to the browser as a download so it is suitable for exporting large datasets (you won't run out of memory).
Excel XML code is based on Excel_XML by Oliver Schwarz (http://github.com/oliverschwarz/php-excel)
## Composer Install
```shell
composer require diego3/php-export-data
```
## How to use it
```php
// When executed in a browser, this script will prompt for download
// of 'test.xls' which can then be opened by Excel or OpenOffice.
use Export\ExportDataExcel;
// 'browser' tells the library to stream the data directly to the browser.
// other options are 'file' or 'string'
// 'test.xls' is the filename that the browser will use when attempting to
// save the download
$exporter = new ExportDataExcel('browser', 'test.xls');
$exporter->initialize(); // starts streaming data to web browser
// pass addRow() an array and it converts it to Excel XML format and sends
// it to the browser
$exporter->addRow(array("This", "is", "a", "test"));
$exporter->addRow(array(1, 2, 3, "123-456-7890"));
// doesn't care how many columns you give it
$exporter->addRow(array("foo"));
$exporter->finalize(); // writes the footer, flushes remaining data to browser.
exit(); // all done
```
See the test/ directory for more examples.
Some other options for creating Excel files from PHP are listed here: http://stackoverflow.com/questions/3930975/alternative-for-php-excel/3931142#3931142

35
lib/php-export-data/composer.json

@ -0,0 +1,35 @@
{
"name": "diego3/php-export-data",
"type": "library",
"description": "A simple library for exporting tabular data to Excel-friendly XML, CSV, or TSV",
"keywords": ["library", "excel", "csv"],
"homepage": "http://github.com/diego3/php-export-data",
"license": "MIT",
"authors": [
{
"name": "Diego Rosa dos Santos",
"email": "www.diegosantos.com.br@gmail.com",
"role": "Developer"
},
{
"name": "Eli Dickinson",
"email": "eli@elidickinson.com",
"role": "Developer"
}
],
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit" : "4.5.*"
},
"autoload": {
"psr-4": {
"Export\\": "src/",
"ExportTest\\": "test/src/"
}
}
}

97
lib/php-export-data/src/ExportData.php

@ -0,0 +1,97 @@
<?php
namespace Export;
/**
* ExportData is the base class for exporters to specific file formats. See other
* classes below.
*
* php-export-data by Eli Dickinson, http://github.com/elidickinson/php-export-data
*
*/
abstract class ExportData {
protected $exportTo; // Set in constructor to one of 'browser', 'file', 'string'
protected $stringData; // stringData so far, used if export string mode
protected $tempFile; // handle to temp file (for export file mode)
protected $tempFilename; // temp file name and path (for export file mode)
public $filename; // file mode: the output file name; browser mode: file name for download; string mode: not used
public function __construct($exportTo = "browser", $filename = "exportdata") {
if (!in_array($exportTo, array('browser', 'file', 'string'))) {
throw new Exception("$exportTo is not a valid ExportData export type");
}
$this->exportTo = $exportTo;
$this->filename = $filename;
}
public function initialize() {
switch ($this->exportTo) {
case 'browser':
$this->sendHttpHeaders();
break;
case 'string':
$this->stringData = '';
break;
case 'file':
$this->tempFilename = tempnam(sys_get_temp_dir(), 'exportdata');
$this->tempFile = fopen($this->tempFilename, "w");
break;
}
$this->write($this->generateHeader());
}
public function addRow($row) {
$this->write($this->generateRow($row));
}
public function finalize() {
$this->write($this->generateFooter());
switch ($this->exportTo) {
case 'browser':
flush();
break;
case 'string':
// do nothing
break;
case 'file':
// close temp file and move it to correct location
fclose($this->tempFile);
rename($this->tempFilename, $this->filename);
break;
}
}
public function getString() {
return $this->stringData;
}
abstract public function sendHttpHeaders();
protected function write($data) {
switch ($this->exportTo) {
case 'browser':
echo $data;
break;
case 'string':
$this->stringData .= $data;
break;
case 'file':
fwrite($this->tempFile, $data);
break;
}
}
protected function generateHeader() {
// can be overridden by subclass to return any data that goes at the top of the exported file
}
protected function generateFooter() {
// can be overridden by subclass to return any data that goes at the bottom of the exported file
}
// In subclasses generateRow will take $row array and return string of it formatted for export type
abstract protected function generateRow($row);
}

24
lib/php-export-data/src/ExportDataCSV.php

@ -0,0 +1,24 @@
<?php
namespace Export;
/**
* ExportDataCSV - Exports to CSV (comma separated value) format.
*/
class ExportDataCSV extends ExportData {
public function generateRow($row) {
foreach ($row as $key => $value) {
// Escape inner quotes and wrap all contents in new quotes.
// Note that we are using \" to escape double quote not ""
$row[$key] = '"' . str_replace('"', '\"', $value) . '"';
}
return implode(",", $row) . "\n";
}
public function sendHttpHeaders() {
header("Content-type: text/csv");
header("Content-Disposition: attachment; filename=" . basename($this->filename));
}
}

182
lib/php-export-data/src/ExportDataExcel.php

@ -0,0 +1,182 @@
<?php
namespace Export;
/**
* ExportDataExcel exports data into an XML format (spreadsheetML) that can be
* read by MS Excel 2003 and newer as well as OpenOffice
*
* Creates a workbook with a single worksheet (title specified by
* $title).
*
* Note that using .XML is the "correct" file extension for these files, but it
* generally isn't associated with Excel. Using .XLS is tempting, but Excel 2007 will
* throw a scary warning that the extension doesn't match the file type.
*
* Based on Excel XML code from Excel_XML (http://github.com/oliverschwarz/php-excel)
* by Oliver Schwarz
*/
class ExportDataExcel extends ExportData {
const XmlHeader = "<?xml version=\"1.0\" encoding=\"%s\"?\>\n<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\" xmlns:x=\"urn:schemas-microsoft-com:office:excel\" xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\" xmlns:html=\"http://www.w3.org/TR/REC-html40\">";
const XmlFooter = "</Workbook>";
public $encoding = 'UTF-8'; // encoding type to specify in file.
// Note that you're on your own for making sure your data is actually encoded to this encoding
public $title = 'Sheet1'; // title for Worksheet
public function generateHeader() {
// workbook header
$output = stripslashes(sprintf(self::XmlHeader, $this->encoding)) . "\n";
// Set up styles
$output .= "<Styles>\n";
// default style
$output .= "<Style ss:ID=\"Default\" ss:Name=\"Normal\"><Alignment ss:Vertical=\"Top\"/></Style>\n";
// multiline text style
$output .= "<Style ss:ID=\"sTXT\"><Alignment ss:Vertical=\"Top\" ss:WrapText=\"1\"/></Style>\n";
// date style
$output .= "<Style ss:ID=\"sDT\"><Alignment ss:Vertical=\"Top\"/><NumberFormat ss:Format=\"Short Date\"/></Style>\n";
// Number style
$output .= "<Style ss:ID=\"sNUM\"><NumberFormat ss:Format=\"Standard\"/></Style>\n";
$output .= "</Styles>\n";
// worksheet header
$output .= sprintf("<Worksheet ss:Name=\"%s\">\n <Table>\n", htmlentities($this->title));
return $output;
}
public function generateFooter() {
$output = '';
// worksheet footer
$output .= " </Table>\n</Worksheet>\n";
// workbook footer
$output .= self::XmlFooter;
return $output;
}
public function generateRow($row) {
$output = '';
$output .= " <Row>\n";
foreach ($row as $k => $v) {
$output .= $this->generateCell($v);
}
$output .= " </Row>\n";
return $output;
}
protected function formatDate($year, $month, $day, $hours, $minutes, $seconds) {
return str_pad(intval($year), 4, 0, STR_PAD_LEFT)
. '-' . str_pad(intval($month), 2, 0, STR_PAD_LEFT)
. '-' . str_pad(intval($day), 2, 0, STR_PAD_LEFT)
. 'T' . str_pad(intval($hours), 2, 0, STR_PAD_LEFT)
. ':' . str_pad(intval($minutes), 2, 0, STR_PAD_LEFT)
. ':' . str_pad(intval($seconds), 2, 0, STR_PAD_LEFT);
}
protected function detectTime($item) {
static $zero = array(0, 0, 0);
if (!strlen(trim($item))){
return $zero;
}
if (preg_match('#(^T|/s+)?([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})\s*$#', $item, $match)) {
array_shift($match);
array_shift($match);
return $match;
}
return false;
}
/**
* Sniff for valid dates; should look something like 2010-07-14 or 14/07/2010 etc. Can
* also have an optional time after the date.
*
* Note we want to be very strict in what we consider a date. There is the possibility
* of really screwing up the data if we try to reformat a string that was not actually
* intended to represent a date.
*
* @param $item
* @return false|string
*/
protected function detectDate($item) {
if ($item = trim($item)) {
if (preg_match('#^([0-9]{1,2})/([0-9]{1,2})/([0-9]{4}|[0-9]{1,2})#', $item, $match)) {
$day = $match[1];
$month = $match[2];
$year = $match[3];
if (strlen($year) <= 2) {
$year = 2000 + $year;
}
if ($time = $this->detectTime(substr($item, strlen($match[0])))) {
list($hours, $minutes, $seconds) = $time;
return $this->formatDate($year, $month, $day, $hours, $minutes, $seconds);
}
} elseif (preg_match('#^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})#', $item, $match)) {
$year = $match[1];
$month = $match[2];
$day = $match[3];
if ($time = $this->detectTime(substr($item, strlen($match[0])))) {
list($hours, $minutes, $seconds) = $time;
return $this->formatDate($year, $month, $day, $hours, $minutes, $seconds);
}
}
}
return false;
}
private function generateCell($item) {
$output = '';
$style = '';
// Tell Excel to treat as a number. Note that Excel only stores roughly 15 digits, so keep
// as text if number is longer than that.
if (preg_match("/^-?\d+(?:[.,]\d+)?$/", $item) && (strlen($item) < 15)) {
$type = 'Number';
// decimal numbers formated as number, but keep integers with default style
if (strpos($item, '.') or strpos($item, ',')) {
$style = 'sNUM';
}
}
elseif ($date = $this->detectDate($item)){
$type = 'DateTime';
$item = $date;
$style = 'sDT'; // defined in header; tells excel to format date for display
} else {
$type = 'String';
}
$item = htmlspecialchars($item, ENT_QUOTES, $this->encoding);
// not necessary, better keeping &#039; for quote
//$item = str_replace('&#039;', '&apos;', $item);
if (!$style and (strpos($item, "\r") !== false or strpos($item, "\n") !== false)) {
$item = str_replace("\r\n", "\n", $item);
$item = str_replace("\r", "\n", $item);
$item = str_replace("\n", "&#013;", $item);
$style = 'sTXT';
}
$output .= " ";
$output .= $style ? "<Cell ss:StyleID=\"$style\">" : "<Cell>";
$output .= sprintf("<Data ss:Type=\"%s\">%s</Data>", $type, $item);
$output .= "</Cell>\n";
return $output;
}
public function sendHttpHeaders() {
header("Content-Type: application/vnd.ms-excel; charset=" . $this->encoding);
header("Content-Disposition: inline; filename=\"" . basename($this->filename) . "\"");
}
}

24
lib/php-export-data/src/ExportDataTSV.php

@ -0,0 +1,24 @@
<?php
namespace Export;
/**
* ExportDataTSV - Exports to TSV (tab separated value) format.
*/
class ExportDataTSV extends ExportData {
public function generateRow($row) {
foreach ($row as $key => $value) {
// Escape inner quotes and wrap all contents in new quotes.
// Note that we are using \" to escape double quote not ""
$row[$key] = '"' . str_replace('"', '\"', $value) . '"';
}
return implode("\t", $row) . "\n";
}
public function sendHttpHeaders() {
header("Content-type: text/tab-separated-values");
header("Content-Disposition: attachment; filename=" . basename($this->filename));
}
}

2
paquet.xml

@ -1,7 +1,7 @@
<paquet
prefix="spip_bonux"
categorie="outil"
version="3.6.0"
version="3.7.0"
etat="stable"
compatibilite="[3.0.0;3.2.*]"
logo="img_pack/spip-bonux.png"

Loading…
Cancel
Save