diff --git a/app/Http/Controllers/TableController.php b/app/Http/Controllers/TableController.php index 9de980e..535554f 100644 --- a/app/Http/Controllers/TableController.php +++ b/app/Http/Controllers/TableController.php @@ -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); + } } diff --git a/app/Tables/BaseExporter.php b/app/Tables/BaseExporter.php new file mode 100644 index 0000000..c5c949c --- /dev/null +++ b/app/Tables/BaseExporter.php @@ -0,0 +1,125 @@ +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 client’s 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(); +} diff --git a/app/Tables/CStructArrayExporter.php b/app/Tables/CStructArrayExporter.php new file mode 100644 index 0000000..d8055ca --- /dev/null +++ b/app/Tables/CStructArrayExporter.php @@ -0,0 +1,118 @@ +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 \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"; + } +} diff --git a/app/Utils/Column.php b/app/Tables/Column.php similarity index 96% rename from app/Utils/Column.php rename to app/Tables/Column.php index 6c172dc..a8c41a0 100644 --- a/app/Utils/Column.php +++ b/app/Tables/Column.php @@ -1,7 +1,7 @@ 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); + } +} diff --git a/app/Tables/JsonExporter.php b/app/Tables/JsonExporter.php new file mode 100644 index 0000000..f95bbfe --- /dev/null +++ b/app/Tables/JsonExporter.php @@ -0,0 +1,44 @@ +iterateRows() as $row) { + if ($first) { + $first = false; + } else { + echo ",\n"; + } + echo json_encode($row, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); + } + + echo "\n]\n"; + } +} diff --git a/public/fonts/fa-dtbl-1-preview.html b/public/fonts/fa-dtbl-1-preview.html index 237ed4b..c171c3a 100644 --- a/public/fonts/fa-dtbl-1-preview.html +++ b/public/fonts/fa-dtbl-1-preview.html @@ -165,6 +165,7 @@ .fa-calendar:before, .fa-code-fork:before, .fa-comment:before, +.fa-download:before, .fa-eye:before, .fa-facebook-square:before, .fa-floppy-o:before, @@ -207,30 +208,31 @@ .fa-calendar:before { content: "\f101"; } .fa-code-fork:before { content: "\f102"; } .fa-comment:before { content: "\f103"; } -.fa-eye:before { content: "\f104"; } -.fa-facebook-square:before { content: "\f105"; } -.fa-floppy-o:before { content: "\f106"; } -.fa-github:before { content: "\f107"; } -.fa-globe:before { content: "\f108"; } -.fa-google:before { content: "\f109"; } -.fa-history:before { content: "\f10a"; } -.fa-home:before { content: "\f10b"; } -.fa-inbox:before { content: "\f10c"; } -.fa-key-modern:before { content: "\f10d"; } -.fa-link:before { content: "\f10e"; } -.fa-pencil:before { content: "\f10f"; } -.fa-question-circle:before { content: "\f110"; } -.fa-sign-in:before { content: "\f111"; } -.fa-sign-out:before { content: "\f112"; } -.fa-star:before { content: "\f113"; } -.fa-star-o:before { content: "\f114"; } -.fa-table:before { content: "\f115"; } -.fa-th-list:before { content: "\f116"; } -.fa-user:before { content: "\f117"; } -.fa-user-circle-o:before { content: "\f118"; } -.fa-user-plus:before { content: "\f119"; } -.fa-users:before { content: "\f11a"; } -.fa-wrench:before { content: "\f11b"; } +.fa-download:before { content: "\f104"; } +.fa-eye:before { content: "\f105"; } +.fa-facebook-square:before { content: "\f106"; } +.fa-floppy-o:before { content: "\f107"; } +.fa-github:before { content: "\f108"; } +.fa-globe:before { content: "\f109"; } +.fa-google:before { content: "\f10a"; } +.fa-history:before { content: "\f10b"; } +.fa-home:before { content: "\f10c"; } +.fa-inbox:before { content: "\f10d"; } +.fa-key-modern:before { content: "\f10e"; } +.fa-link:before { content: "\f10f"; } +.fa-pencil:before { content: "\f110"; } +.fa-question-circle:before { content: "\f111"; } +.fa-sign-in:before { content: "\f112"; } +.fa-sign-out:before { content: "\f113"; } +.fa-star:before { content: "\f114"; } +.fa-star-o:before { content: "\f115"; } +.fa-table:before { content: "\f116"; } +.fa-th-list:before { content: "\f117"; } +.fa-user:before { content: "\f118"; } +.fa-user-circle-o:before { content: "\f119"; } +.fa-user-plus:before { content: "\f11a"; } +.fa-users:before { content: "\f11b"; } +.fa-wrench:before { content: "\f11c"; } @@ -246,7 +248,7 @@
-

fa-dtbl-1 contains 28 glyphs:

+

fa-dtbl-1 contains 29 glyphs:

Toggle Preview Characters
@@ -304,6 +306,19 @@
+
+
+ PpPpPpPpPpPpPpPpPpPp +
+
+ 12141618212436486072 +
+
+ + +
+
+
PpPpPpPpPpPpPpPpPpPp @@ -313,7 +328,7 @@
- +
@@ -326,7 +341,7 @@
- +
@@ -340,7 +355,7 @@
- +
@@ -353,7 +368,7 @@
- +
@@ -366,7 +381,7 @@
- +
@@ -379,7 +394,7 @@
- +
@@ -392,7 +407,7 @@
- +
@@ -405,7 +420,7 @@
- +
@@ -418,7 +433,7 @@
- +
@@ -431,7 +446,7 @@
- +
@@ -444,7 +459,7 @@
- +
@@ -457,7 +472,7 @@
- +
@@ -470,7 +485,7 @@
- +
@@ -483,7 +498,7 @@
- +
@@ -496,7 +511,7 @@
- +
@@ -509,7 +524,7 @@
- +
@@ -522,7 +537,7 @@
- +
@@ -535,7 +550,7 @@
- +
@@ -548,7 +563,7 @@
- +
@@ -561,7 +576,7 @@
- +
@@ -574,7 +589,7 @@
- +
@@ -587,7 +602,7 @@
- +
@@ -600,7 +615,7 @@
- +
@@ -613,7 +628,7 @@
- +
diff --git a/public/fonts/fa-dtbl-1.css b/public/fonts/fa-dtbl-1.css index f9433b7..134b96f 100644 --- a/public/fonts/fa-dtbl-1.css +++ b/public/fonts/fa-dtbl-1.css @@ -42,27 +42,28 @@ .fa-calendar::before { content: "\f101"; } .fa-code-fork::before { content: "\f102"; } .fa-comment::before { content: "\f103"; } -.fa-eye::before { content: "\f104"; } -.fa-facebook-square::before { content: "\f105"; } -.fa-floppy-o::before, .fa-save::before { content: "\f106"; } -.fa-github::before { content: "\f107"; } -.fa-globe::before { content: "\f108"; } -.fa-google::before { content: "\f109"; } -.fa-history::before { content: "\f10a"; } -.fa-home::before { content: "\f10b"; } -.fa-inbox::before { content: "\f10c"; } -.fa-key-modern::before { content: "\f10d"; } -.fa-link::before { content: "\f10e"; } -.fa-pencil::before { content: "\f10f"; } -.fa-question-circle::before { content: "\f110"; } -.fa-sign-in::before { content: "\f111"; } -.fa-sign-out::before { content: "\f112"; } -.fa-star::before { content: "\f113"; } -.fa-star-o::before { content: "\f114"; } -.fa-table::before { content: "\f115"; } -.fa-th-list::before { content: "\f116"; } -.fa-user::before { content: "\f117"; } -.fa-user-circle-o::before { content: "\f118"; } -.fa-user-plus::before { content: "\f119"; } -.fa-users::before { content: "\f11a"; } -.fa-wrench::before { content: "\f11b"; } +.fa-download::before { content: "\f104"; } +.fa-eye::before { content: "\f105"; } +.fa-facebook-square::before { content: "\f106"; } +.fa-floppy-o::before, .fa-save::before { content: "\f107"; } +.fa-github::before { content: "\f108"; } +.fa-globe::before { content: "\f109"; } +.fa-google::before { content: "\f10a"; } +.fa-history::before { content: "\f10b"; } +.fa-home::before { content: "\f10c"; } +.fa-inbox::before { content: "\f10d"; } +.fa-key-modern::before { content: "\f10e"; } +.fa-link::before { content: "\f10f"; } +.fa-pencil::before { content: "\f110"; } +.fa-question-circle::before { content: "\f111"; } +.fa-sign-in::before { content: "\f112"; } +.fa-sign-out::before { content: "\f113"; } +.fa-star::before { content: "\f114"; } +.fa-star-o::before { content: "\f115"; } +.fa-table::before { content: "\f116"; } +.fa-th-list::before { content: "\f117"; } +.fa-user::before { content: "\f118"; } +.fa-user-circle-o::before { content: "\f119"; } +.fa-user-plus::before { content: "\f11a"; } +.fa-users::before { content: "\f11b"; } +.fa-wrench::before { content: "\f11c"; } diff --git a/public/fonts/fa-dtbl-1.eot b/public/fonts/fa-dtbl-1.eot index 39d233d..3083190 100644 Binary files a/public/fonts/fa-dtbl-1.eot and b/public/fonts/fa-dtbl-1.eot differ diff --git a/public/fonts/fa-dtbl-1.svg b/public/fonts/fa-dtbl-1.svg index dbd2c25..d2153ea 100644 --- a/public/fonts/fa-dtbl-1.svg +++ b/public/fonts/fa-dtbl-1.svg @@ -5,7 +5,7 @@ --> -Created by FontForge 20170805 at Sun Jul 29 16:55:50 2018 +Created by FontForge 20170805 at Sun Jul 29 20:18:03 2018 By ondra The Fork Awesome font is licensed under the SIL OFL 1.1 (http://scripts.sil.org/OFL). Fork Awesome is a fork based of off Font Awesome 4.7.0 by Dave Gandy. More info on licenses at https://forkawesome.github.io @@ -22,7 +22,7 @@ The Fork Awesome font is licensed under the SIL OFL 1.1 (http://scripts.sil.org/ bbox="-0.14014 -256.168 2048 1536.01" underline-thickness="89.6" underline-position="-179.2" - unicode-range="U+0020-F11B" + unicode-range="U+0020-F11C" /> - + - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/fonts/fa-dtbl-1.ttf b/public/fonts/fa-dtbl-1.ttf index e30cf85..24830b5 100644 Binary files a/public/fonts/fa-dtbl-1.ttf and b/public/fonts/fa-dtbl-1.ttf differ diff --git a/public/fonts/fa-dtbl-1.woff2 b/public/fonts/fa-dtbl-1.woff2 index 65eea52..49d56af 100644 Binary files a/public/fonts/fa-dtbl-1.woff2 and b/public/fonts/fa-dtbl-1.woff2 differ diff --git a/resources/views/table/_panel-export.blade.php b/resources/views/table/_panel-export.blade.php new file mode 100644 index 0000000..4048146 --- /dev/null +++ b/resources/views/table/_panel-export.blade.php @@ -0,0 +1,22 @@ +Export presets: + + diff --git a/resources/views/table/view.blade.php b/resources/views/table/view.blade.php index 5f517f0..ec2ae45 100644 --- a/resources/views/table/view.blade.php +++ b/resources/views/table/view.blade.php @@ -35,14 +35,14 @@
- Export … coming soon + @include('table._panel-export')
{{-- End of tab panels wrapper --}} {{-- Right column with menu --}}
-