rudimentary export options

This commit is contained in:
2018-07-29 20:19:19 +02:00
parent 3bf9b68503
commit 1037f438ce
15 changed files with 522 additions and 103 deletions
+34 -1
View File
@@ -6,10 +6,14 @@ use App\Models\Revision;
use App\Models\Row;
use App\Models\Table;
use App\Models\User;
use App\Utils\Column;
use App\Tables\Column;
use App\Tables\CStructArrayExporter;
use App\Tables\CsvExporter;
use App\Tables\JsonExporter;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use MightyPork\Exceptions\NotApplicableException;
use MightyPork\Utils\Utils;
class TableController extends Controller
{
@@ -258,4 +262,33 @@ class TableController extends Controller
}
}
}
public function export(Request $request, User $user, string $table)
{
/** @var Table $tableModel */
$tableModel = $user->tables()->where('name', $table)->first();
if ($tableModel === null) abort(404, "No such table.");
$exporter = null;
switch ($request->get('format')) {
case 'json':
$exporter = new JsonExporter($tableModel);
break;
case 'csv':
$exporter = new CsvExporter($tableModel);
break;
case 'c':
$exporter = new CStructArrayExporter($tableModel);
break;
default:
abort(400, "Unspecified or unknown format.");
}
$dl = Utils::parseBool($request->get('dl', false));
$exporter->exportToBrowser($dl);
}
}
+125
View File
@@ -0,0 +1,125 @@
<?php
namespace App\Tables;
use App\Models\Table;
/**
* Table exporter base class
*/
abstract class BaseExporter
{
/**
* @var Table the table to export
*/
protected $table;
/**
* @var Column[] - parsed columns for the table
*/
protected $columns;
/**
* @var bool - true if user wants download; can e.g. change mime type to text/plain if not
*/
protected $wantDownload;
/**
* TODO export options from enabled columns, sorts, etc
*
* @param Table $table
*/
public function __construct(Table $table)
{
$this->table = $table;
$this->columns = Column::columnsFromJson($table->revision->columns);
}
/**
* @return string - mime type for the downloaded file
*/
protected abstract function getMimeType();
/**
* @return string - file extension for the downloaded file
*/
protected abstract function getFileExtension();
/**
* @return string - file name without extension, by default the table name
*/
protected function getFileBasename()
{
return $this->table->name;
}
/**
* Generate a response that appears as a file download to the browser
*
* @param bool $download - force download, otherwise the browser may show it in the window
*/
public function exportToBrowser($download = true)
{
$this->wantDownload = $download;
$mimeType = $this->getMimeType();
$filename = $this->getFileBasename() . '.' . $this->getFileExtension();
ob_end_clean();
// Redirect output to a clients web browser
header("Content-Type: $mimeType; charset=utf-8");
if ($download) {
header("Content-Disposition: attachment;filename=\"$filename\"");
}
// Cache headers
header('Cache-Control: max-age=0');
// IE9
header('Cache-Control: max-age=1');
// Other IE headers
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
header('Pragma: no-cache'); // HTTP/1.0
$this->writeDocument();
exit;
}
/**
* Generator that produces PHP arrays for all rows, fetched from the DB in given chunks.
*
* @param int $chunkSize - size of one chunk
* @return \Generator|array[]
*/
protected function iterateRows($chunkSize = 1000)
{
$revision = $this->table->revision;
$count = $revision->rows()->count();
$start = 0;
while ($start < $count) {
$rows = $revision->rows()->offset($start)->limit($chunkSize)->get();
foreach ($rows as $row) {
$data = json_decode($row->data);
// column renaming, value formatting...
yield $data;
}
$start += $chunkSize;
}
}
/**
* Write the document to stdout ('php://output')
*/
protected abstract function writeDocument();
}
+118
View File
@@ -0,0 +1,118 @@
<?php
namespace App\Tables;
class CStructArrayExporter extends BaseExporter
{
/**
* @return string - mime type for the downloaded file
*/
protected function getMimeType()
{
return $this->wantDownload ? 'text/x-csrc' : 'text/plain';
}
/**
* @return string - file extension for the downloaded file
*/
protected function getFileExtension()
{
return 'c';
}
private function cify($name)
{
$name = preg_replace('/[^a-z0-9_]/i', '_', $name);
if (ctype_digit($name[0])) {
$name = '_' . $name;
}
return $name;
}
/**
* Write the document to stdout ('php://output')
*/
protected function writeDocument()
{
$fields = array_map(function (Column $c) {
$type = $c->type;
if ($type == 'string') {
$type = 'const char *';
}
$name = $this->cify($c->name);
return (object)['name' => $c->name, 'type' => $c->type, 'ctype' => $type, 'cname' => $name];
}, $this->columns);
$ctablename = $this->cify($this->table->name);
// preamble
echo "#include <stdbool.h>\n\n";
echo "struct " . $ctablename . " {\n";
foreach ($fields as $field) {
echo " " . $field->ctype . " " . $field->cname . ";\n";
}
echo "};\n\n";
// Now the table itself
echo "const struct ".$ctablename.' tbl_'.$ctablename."[] = {\n";
$first = true;
foreach ($this->iterateRows() as $row) {
if ($first) {
$first = false;
} else {
echo ",\n";
}
echo " {";
$firstf = true;
foreach ($fields as $field) {
if ($firstf) {
$firstf = false;
} else {
echo ", ";
}
$val = 0;
switch ($field->type) {
case 'string':
$val = "";
break;
case 'bool':
$val = false;
break;
}
if (isset($row->{$field->name})) {
$val = $row->{$field->name};
}
// export to C format
switch ($field->type) {
case 'string':
echo '"' . addcslashes($val, "\0..\37!@\@\177..\377") . '"';
break;
case 'bool':
echo (int)$val;
break;
case 'int':
case 'float':
echo $val;
break;
}
}
echo "}";
}
echo "\n};\n";
}
}
@@ -1,7 +1,7 @@
<?php
namespace App\Utils;
namespace App\Tables;
use JsonSerializable;
@@ -27,6 +27,10 @@ class Column implements JsonSerializable
public static function columnsFromJson($columns)
{
if (is_string($columns)) {
$columns = json_decode($columns);
}
return array_map(function ($x) {
return new Column($x);
}, $columns);
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace App\Tables;
class CsvExporter extends BaseExporter
{
/**
* @return string - mime type for the downloaded file
*/
protected function getMimeType()
{
return $this->wantDownload ? 'text/csv' : 'text/plain';
}
/**
* @return string - file extension for the downloaded file
*/
protected function getFileExtension()
{
return 'csv';
}
/**
* Write the document to stdout ('php://output')
*/
protected function writeDocument()
{
$handle = fopen('php://output', 'w');
$columnNames = array_map(function (Column $c) {
return $c->title;
}, $this->columns);
fputcsv($handle, $columnNames);
foreach ($this->iterateRows() as $row) {
$items = [];
foreach ($this->columns as $column) {
if (isset($row->{$column->name})) {
$items[] = $row->{$column->name};
} else {
$items[] = null;
}
}
fputcsv($handle, $items);
}
fclose($handle);
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace App\Tables;
class JsonExporter extends BaseExporter
{
/**
* @return string - mime type for the downloaded file
*/
protected function getMimeType()
{
return 'application/json';
}
/**
* @return string - file extension for the downloaded file
*/
protected function getFileExtension()
{
return 'json';
}
/**
* Write the document to stdout ('php://output')
*/
protected function writeDocument()
{
echo "[\n";
$first = true;
foreach ($this->iterateRows() as $row) {
if ($first) {
$first = false;
} else {
echo ",\n";
}
echo json_encode($row, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
}
echo "\n]\n";
}
}