Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sideeffect42 committed Jan 14, 2023
0 parents commit c137073
Show file tree
Hide file tree
Showing 52 changed files with 2,039 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
end_of-line = lf
charset = utf-8

[Makefile]
indent_style = tab

[*.php]
indent_style = tab
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/csvreader.php
/vendor/
*~
18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
PHP ?= php

SRC_DIR = src
TESTS_DIR = tests

all: csvreader.php

csvreader.php: .FORCE
$(PHP) compile.php $(SRC_DIR)/CSVReader.php >$@

test: .FORCE
@if test -n '$(PHPUNIT)'; then set -- $(PHPUNIT); elif test -x vendor/bin/phpunit; then set -- vendor/bin/phpunit; else set -- phpunit; fi; set -- "$$@" --test-suffix .php -vv $(TESTS_DIR); echo "$$@"; "$$@"

sast: .FORCE
@if test -n '$(PHPSTAN)'; then set -- $(PHPSTAN); elif test -x vendor/bin/phpstan; then set -- vendor/bin/phpstan; else set -- phpstan; fi; set -- "$$@" analyse --no-progress --level=8 $(SRC_DIR); echo "$$@"; "$$@"


.FORCE:
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# php-csvreader

This PHP library is an opinionated wrapper around PHP's `fgetcsv()` function
trying to be suitable for guessing (column and row) separators and file encoding
as generated by typical spreadsheet applications (looking at you, Excel).
The heuristics used to guess the encoding detects Unicode encodings correctly.
For legacy code pages, Western European encodings are detected.

Using this library for non-commercial or free software projects is permissible in accordance with the [GNU General Public License (version 3 or later)](https://www.gnu.org/licenses/gpl-3.0.en.html).


## Installation

### Classic

The whole library can be compiled into a single PHP file which can be `require`d
from any PHP module:

```console
$ make
$ php <<EOF
<?php
require 'csvreader.php';

// ...
EOF
```


### Composer

In your project's `composer.json` add the following configuration options and
run `composer install`:

```json
{
"repositories": [
{
"type": "github",
"url": "https://github.com/riiengineering/php-csvreader"
}
],
"require": {
"riiengineering/php-csvreader": "dev-main"
}
}
```


## Usage

```php
<?php
require_once 'csvreader.php'; /* if using the classic method */
require_once 'vendor/autoload.php'; /* if using composer */

$reader = new \riiengineering\csvreader\CSVReader(
$file,
array(
'id',
'product',
'currency',
'price',
),
array(
'separator' => ';',
'encoding' => 'AUTO',
'line-separator' => 'AUTO',
));

foreach ($reader as $row) {
var_dump($row) . PHP_EOL;
}
```

-----
[![riiengineered.](https://www.riiengineering.ch/riiengineered-400.png)](//www.riiengineering.ch)
154 changes: 154 additions & 0 deletions compile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#!/usr/bin/env php
<?php

$php_header_ended = FALSE;
$php_declares = array();
$php_includes = array();

function fatal(string $s): void {
fwrite(STDERR, $s);
exit(1);
}

function php_to_path(string $code, string $file): string {
# NOTE: this function only does some very very basic PHP parsing that should
# be good enough for the average include/require call.

if (FALSE !== strpos($code, '__DIR__')) {
$code = preg_replace(
'/(^|[ \t]*\.|[a-z_]*\()[ \t]*__DIR__[ \t]*(\.[ \t]*|\)|$)/',
'$1\''.dirname($file).'\'$2',
$code);
}

if (FALSE !== strpos($code, '__FILE__')) {
$code = preg_replace(
'/(^|[ \t]*\.|[a-z_]*\()[ \t]*__FILE__[ \t]*(\.[ \t]*|\)|$)/',
'$1\''.$file.'\'$2',
$code);
}

return eval("return(${code});");
}

function php_declare(string $k, string $v): void {
global $php_declares, $php_header_ended;

if (array_key_exists($k, $php_declares)) {
if ($v === $php_declares[$k]) {
return;
} else {
fatal("${k} is already declared with a different value\n");
}
}
if ($php_header_ended) {
fatal("declare too late\n");
}

$php_declares[$k] = $v;
printf("declare(%s=%s);\n", $k, $v);
}

function process_file(string $filename): void {
global $php_header_ended, $php_includes;

array_push($php_includes, realpath($filename));

$fh = fopen($filename, 'r');
if (!$fh) {
fatal("failed to process ${filename}\n");
}

$php = FALSE;
$ns = '';
$nsopen = 0;

// echo "// file:${filename}\n";

while (FALSE !== ($line = fgets($fh))) {
$line = rtrim($line, "\n");

if (!$php) {
if (preg_match('/<\?php/', $line)) {
$php = TRUE;
$line = preg_replace('/^.*?<\?php/', '', $line);
} else {
continue;
}
}

if (preg_match('/\?>/', $line)) {
fatal("mixed source detected\n");
}

if (preg_match('/^\s*declare\s*\(/', $line)) {
while (preg_match('/^\s*declare\s*\((.*?)\)\s*;\s*/', $line, $matches)) {
call_user_func_array(
'php_declare',
preg_split('/=/', $matches[1], 2));
$line = substr($line, strlen($matches[0]));
}
}

$php_header_ended = TRUE;

if (preg_match('/namespace\s+([A-Za-z0-9\\\\]+)\s*([;{])\s*/', $line, $matches)) {
for (; 0 < $nsopen; --$nsopen) echo "}\n";

if (';' === $matches[2]) {
$line = substr($line, strlen($matches[0]));
$ns = $matches[1];
} else {
$ns = NULL;
}
}

if (preg_match('/^\s*((?:include|require)(?:_once)?)\s*(\(.*\)|.*)\s*;\s*/', $line, $matches)) {
$path = $matches[2];
if ('(' === $path[0] && ')' === $path[-1]) $path = substr($path, 1, strlen($path)-2);

$path = php_to_path($path, $filename);

if ('_once' !== substr($matches[1], -4) || !in_array($path, $php_includes)) {
if (file_exists($path)) {
for (; 0 < $nsopen; --$nsopen) echo "}\n";
process_file($path);
} else {
fwrite(STDERR, "${path}: file to include not found\n");
if (0 === strpos($matches[1], 'require')) {
exit(1);
}
}
}

$line = substr($line, strlen($matches[0]));
}

if (trim($line)) {
if (!$nsopen && !is_null($ns)) {
echo $ns ? "namespace ${ns} {\n" : "namespace {\n";
++$nsopen;
}
echo $line."\n";
}
}

for (; 0 < $nsopen; --$nsopen) echo "}\n";

// echo "// endf:${filename}\n";

fclose($fh);
}

function main(array $argv): int {
echo "<?php\n";

array_shift($argv); // the compiler script itself
foreach ($argv as $arg) {
process_file($arg);
}

return 0;
}

exit(main($argv));
27 changes: 27 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "riiengineering/csvreader",
"description": "Opinionated CSV reader library tuned to the specifics of spreadsheet applications",
"type": "library",
"readme": "README.md",
"license": "GPL-3.0-or-later",
"authors": [
{
"name": "Dennis Camera",
"email": "dennis.camera@riiengineering.ch",
"homepage": "http://www.riiengineering.ch",
"role": "developer"
}
],
"require": {
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "*",
"phpstan/phpstan": "*"
},
"autoload": {
"classmap": [
"src/CSVReader.php"
]
}
}
Loading

0 comments on commit c137073

Please sign in to comment.