120 Commits
1.0.0 ... 1.2.0

Author SHA1 Message Date
Fonata
548a9fe551 ChangeLog: Added release date 2020-01-07 09:32:12 +01:00
susgo
5336800ec6 Merge pull request #186 from parsecsv/bu-object-test
Added test for the case of objects as cells
2020-01-06 19:35:23 +01:00
susgo
6bd53940c5 Merge pull request #185 from parsecsv/release-1.2.0
Release candidate 1.2.0
2020-01-06 19:32:41 +01:00
Fonata
a9297449b9 Autoloading: No need to explicitly mention ParseCsv\extensions
It is a sub-directory of src anyway.
2020-01-06 19:04:58 +01:00
Fonata
c12b6ba671 Added test for the case of objects as cells
I encountered this in Drupal 8, where cells implemented the
MarkupInterface. It was objects that, when cast to a string,
returned the translated string.

Before 5ca540daa7, this new test
would fail.
2020-01-06 18:12:37 +01:00
Fonata
b792a6cc7b Code quality: Removed superfluous brackets; should not change anything 2020-01-06 18:04:45 +01:00
Fonata
af442cdd0e PHP closing tags are no longer common practise 2019-12-25 15:09:31 +01:00
Fonata
95810ec2fa Updated ChangeLog.txt 2019-12-25 15:08:37 +01:00
Fonata
d79160f40a Examples: Removed deprecated HTTML attributes from <table> tag 2019-12-25 15:06:18 +01:00
Fonata
7ba2f9b296 Added an example for a verbal comparison 2019-12-25 15:04:07 +01:00
Fonata
d7ba892d38 Bug fix for $csv->conditions = 'rating is LESS than 4'
The $op was wrong because of upper case
2019-12-25 14:54:50 +01:00
Jess
38e9fff285 Added financial contributors to the README 2019-12-25 07:57:32 -05:00
Fonata
174d2e6070 Merge branch 'andreybolonin-patch-1' 2019-12-25 13:49:08 +01:00
Andrey Bolonin
d93036ca87 Update .travis.yml 2019-12-25 13:47:59 +01:00
Craig Morris
62fc367c14 Run https://gist.github.com/theodorejb/763b83a43522b0fc1755a537663b1863 2019-12-25 07:45:00 -05:00
susgo
044122efb9 Merge pull request #180 from parsecsv/more-conditions-bu
Bugfix for verbal conditions
2019-11-04 21:20:58 +01:00
susgo
18cbc3ab87 Merge pull request #174 from parsecsv/code-quality-and-documentation
Code quality and documentation
2019-11-04 21:19:35 +01:00
Fonata
af75206e86 Travis dist implicitly switched to Ubuntu Xenial, which didn't have PHP 5.5 2019-11-03 23:19:51 +01:00
Fonata
e45eb18213 Bugfix: All operators containing "is" or "equals" were broken
Fixes #179
2019-11-03 23:09:23 +01:00
Fonata
5340ab9584 New feature: Verbal condition operators don't have to be lower case 2019-11-03 23:08:08 +01:00
Fonata
f3601dbfb5 PHP 5.5 was not available; attempt to use Ubuntu Trusty 2019-09-14 09:01:10 +02:00
Fonata
67c8a0167c Added test for 1 MB stream data 2019-09-14 04:17:58 +02:00
Fonata
8b583daa08 Improved documentation comments 2019-09-14 04:10:58 +02:00
Fonata
b6dccf5fa6 Use in_array() to make code more readable 2019-09-14 03:56:00 +02:00
Fonata
5ca540daa7 Avoid PhpStorm inspection for $value{0} on empty string (prevent E_NOTICE)
Here, $value is not empty, but the PhpStorm inspection doesn't get that.
I want to have zero warnings for this library.
2019-09-14 01:19:40 +02:00
Fonata
f89ed5978d Simplified RegEx: {1} is the default and can be dropped 2019-09-14 01:17:09 +02:00
Fonata
f053cdde80 New feature: unparse now also understands $use_mb_convert_encoding 2019-09-14 01:14:41 +02:00
Fonata
62ae4e5fc3 Formated code using PhpStorm 2019.2.1 - only DocBlocks changed 2019-09-14 01:05:21 +02:00
susgo
a286556c16 Merge pull request #171 from parsecsv/travis-conf-bu
Travis conf didn't match schema
2019-07-27 12:11:55 +02:00
Fonata
7326262304 Travis conf didn't match schema
- PHP versions need to be strings
- https://docs.travis-ci.com/user/notifications/#configuring-email-notifications

Also: tell editors like PhpStorm how the YAML needs to be formatted.
2019-07-26 21:55:06 +02:00
susgo
b746ea1de0 Merge pull request #166 from parsecsv/parse_string_internal
End users should not be tempted to call parse_string.
2019-05-11 14:19:52 +02:00
Fonata
ef5e81f837 Added test for our code example in issue #165
Closes #165
2019-03-22 08:17:55 +01:00
Fonata
637f79c2e6 Improved documentation and parameter declaration
No functional changes.
2019-03-22 08:17:55 +01:00
Fonata
e3105d4003 Renamed protected functions: no end user should be tempted to call them 2019-03-22 08:17:55 +01:00
susgo
5874b768b9 Merge pull request #164 from parsecsv/composer-validate
Quality improvement, but no bug fixes or new features
2019-02-24 05:47:39 +01:00
Fonata
5d766adc17 PHPUnit versions newer than 8 don't like our setUp function
See https://travis-ci.org/parsecsv/parsecsv-for-php/jobs/497446669
2019-02-23 13:21:54 +01:00
Fonata
67ed765eff Added PHP 7.3 to test coverage 2019-02-23 13:09:09 +01:00
Fonata
a7be07cc02 PHP 7.2.15 doesn't seem to like public setUp functions 2019-02-23 13:06:28 +01:00
Fonata
b4cef37bb5 Simplyfied BaseClass.php: @access annotations are not needed 2019-02-23 13:05:55 +01:00
Fonata
f97f03a088 Only improved code formatting 2019-02-23 12:57:48 +01:00
Fonata
2e94f4ad41 Add composer validation to CI;
Also: add branch alias for `dev-master`

Fix #159
2019-02-23 12:53:58 +01:00
Fonata
d03f8a2f09 Simplified code; shouldn't change results 2019-02-23 12:48:52 +01:00
Fonata
14c9254ed4 Removed @access annotations because they don't add value
I believe they come from the time when PHP didn't universally
support the language keywords. phpDocumentor doesn't need the
@access annotations.
2019-02-23 12:45:28 +01:00
Fonata
78c288db8c Improved documentation comment 2019-02-23 12:45:07 +01:00
Fonata
531ac26e11 Improved spelling in comments 2019-02-23 12:44:31 +01:00
susgo
65566adcd3 Merge pull request #162 from parsecsv/add-save-example
Added example for writing CSV files to better document heading property
2019-02-02 20:40:45 +01:00
Fonata
2d428ffa93 Updated test: no output in save_to_file_without_header_row.php
Thus, effectively, we just test for valid PHP syntax.
2019-02-02 18:47:55 +01:00
Fonata
bc9207de09 Added example for writing CSV files to better document heading property
Fixes #161
2019-02-02 18:39:21 +01:00
susgo
ace09c3c11 Update README.md
Update examples for #155
2018-11-14 18:30:03 +01:00
Susann Sgorzaly
52ad56c66a fixes unparse bug if no data for unparsing remain (comments #150) 2018-11-14 08:26:39 +01:00
Susann Sgorzaly
ab9e8a0af9 fixes unparse bug if array ids doesn't begin on zero (comments#149) 2018-11-14 08:26:39 +01:00
Fonata
8aa61914f7 Function load_data: check length of input, prevents E_NOTICE if too long
Fixes #151
2018-09-25 00:35:03 +02:00
Fonata
3b74f7ce57 Added PHPUnit test to make sure very long text doesn't cause E_NOTICE 2018-09-25 00:27:04 +02:00
Fonata
c7375dea8e Added authors and web links to source and issues 2018-08-09 13:48:06 +02:00
Fonata
6ce4550eed Merge pull request #147 from parsecsv/file_get_contents
Replace fread with file_get_contents to avoid the 8192 byte limit
2018-08-08 21:02:26 +02:00
Fonata
55890da647 Replace fread with file_get_contents to avoid the 8192 byte limit
This can happen when using streams.

Specific problem occurred with a file uploaded with Drupal's ``managed_file``
Form API element.

See https://secure.php.net/manual/en/function.fread.php
2018-07-31 21:59:04 +02:00
Fonata
21953ab6be Merge pull request #146 from parsecsv/waiver-test
Added test for issue 145, couldn't reproduce it
2018-05-22 09:01:01 +02:00
Fonata
1d8e6808bc Added test for issue #145, couldn't reproduce it 2018-05-21 21:56:54 +02:00
Fonata
063a812732 Merge pull request #142 from parsecsv/issue-141-multiple-empty-lines
Ignore entirely empty lines at the end of files
2018-05-11 09:03:36 +02:00
Fonata
4504f4b158 Converted function names to camelCase for PSR karma points 2018-04-29 11:21:00 +02:00
Fonata
b82ad03bd5 Ignore entirely empty lines at the end of files
Closes #141
2018-04-29 11:08:40 +02:00
Fonata
23a1b0a4b3 Merge pull request #138 from geminorum/master
min php on composer.json
2018-04-11 08:47:06 +02:00
Fonata
51d878a9a1 Merge pull request #137 from parsecsv/new-unparse-test
Added new test for unparse with parameters
2018-04-11 08:31:33 +02:00
Fonata
7d39b80fca Merge pull request #140 from susgo/patch-1
Update README.md: corrected function name getTotalDataRowCount()
2018-04-11 08:30:21 +02:00
susgo
8c7eba6815 Update README.md 2018-04-05 16:32:44 +02:00
Nasser Rafie
c41c559ac2 min php on composer.json 2018-04-02 18:36:16 +04:30
Fonata
e1ecf9302e Advertised getCollection in ChangeLog.txt 2018-04-02 15:11:17 +02:00
Fonata
de8e792ef4 Merge branch 'feature-datacollections'
Conflicts:
	src/Csv.php
2018-04-02 14:40:32 +02:00
Fonata
60d6458080 PHPUnit: prevent output from download.php to leak 2018-04-02 14:38:03 +02:00
William Knauss
406e1e415f added getCollection method that returns a Illuminate\Support\Collection object - useful for using the mapping functions 2018-04-02 14:32:17 +02:00
Fonata
2e40a2eb1c Added new test for unparse with parameters 2018-03-28 15:45:44 +02:00
Fonata
927e785891 Merge pull request #133 from parsecsv/fixing-132-output-with-fields
Bug fix: calling ->output() with $fields parameter set
2018-03-28 13:25:05 +02:00
Fonata
e6ba24b47a Dropping PHP 5.4 support (#131)
Also removed special treatment of PHP 5.4, as we don't support it anymore
2018-03-20 11:12:22 +01:00
Fonata
f24e5bf3a3 Merge branch 'offset-comment-and-tests' 2018-03-17 12:44:02 +01:00
Fonata
bdd4e5ef25 Merge branch 'master' into offset-comment-and-tests
Conflicts:
	tests/methods/ParseTest.php
2018-03-17 12:42:18 +01:00
Fonata
42b5d30d66 Improved OldRequireTest: explanation and new test added (#128) 2018-03-17 12:37:54 +01:00
Fonata
93f177a396 Merged two ifs in _validate_row_condition to prevent
This is to a sane count of zero PhpStorm inspections. In particular,
this is NestedPositiveIfStatementsInspection from EA Extended.
2018-03-17 12:17:25 +01:00
Fonata
f5ff7332a4 Set $data field on unparse. This fixes issue #132. 2018-03-17 12:16:39 +01:00
Fonata
07846d33c1 Added OutputTest to verify issue #132 is a bug. 2018-03-17 12:07:12 +01:00
Fonata
4b60d38fb0 Tell PhpStorm not to complain about reference mismatch
http://blog.jpauli.tech/2014/06/27/references-mismatch.html#what-is-a-reference-mismatch
2018-03-17 12:05:01 +01:00
Fonata
c8d15557cb Fixed parse()'s return value: return true only if $data is useful 2018-03-17 12:05:01 +01:00
Fonata
10895788c8 Slightly simplified getTotalDataRowCount, added test
The brackets () in the pattern were not needed, as only
$matches[0] was accessed, not $matches[1].

The @var is useful for PhpStorm's Php Inspections (EA Extended).
More details here: https://github.com/kalessil/phpinspectionsea/blob/master/docs/types-compatibility.md#foreach-source-to-iterate-over
2018-03-17 12:05:01 +01:00
Fonata
ffed7ffdc0 Only improved PHPDoc blocks, including sentance for output() 2018-03-17 12:05:01 +01:00
Fonata
7498a963ac _validate_fields_for_unparse: more info in UnexpectedValueException 2018-03-17 11:42:44 +01:00
Fonata
49728a74bb Only reformatted the source code 2018-03-17 10:31:09 +01:00
susgo
e4c9fed6cf Update README.md for old and new functions (#129)
* Update README.md
2018-03-12 08:28:25 +01:00
Susann Sgorzaly
5bc6d09b5e fixes bug on _validate_fields_for_unparse() if titles property is used instead of fields parameter for changing the titles for unparsing 2018-03-12 08:25:42 +01:00
susgo
b7f2075efc Fix #41: output order and subset (#126)
* init unparse tests for ordering and subseting by fields
* added one test for heading=false
* implements functionality of this issue
2018-03-11 11:14:53 +01:00
Christian Bläul
3cc20fbb47 Merge remote-tracking branch 'itexia/auto-detect-file-has-heading'
# Conflicts:
#	tests/methods/ParseTest.php
2018-03-10 12:46:36 +01:00
Christian Bläul
086cd15b44 Added requires to keep Composer-free environments working 2018-03-07 09:51:50 +01:00
Christian Bläul
03bc946b98 Sorted uses alphabetically, removed version
The repo is no longer on version 1.0.0. The ChangeLog.txt file is a
more obvious place for people to see which version includes what.
2018-03-07 09:45:48 +01:00
Christian Bläul
8f431aa7a4 Merge remote-tracking branch 'itexia/refactoring-constants' 2018-03-07 09:43:22 +01:00
Christian Bläul
7168cb15e3 ChangeLog.txt: Added new feature 2018-03-07 09:40:43 +01:00
Fonata
1142ef2611 Merge pull request #124 from itexia/new-enums-for-docu
New Enum class for file processing (save + unparse) + Documentation
2018-03-07 09:35:01 +01:00
Fonata
80a4954a3a Merge pull request #122 from itexia/get-total-row-count
New feature: get total row count - useful if $limit is set.
2018-03-07 09:30:36 +01:00
Christian Bläul
9d7ccab20f Improved comment and test data to show how the offset is counted. 2018-03-02 17:50:01 +01:00
Susann Sgorzaly
fb9325884d small code improvement 2018-02-28 13:47:38 +01:00
Susann Sgorzaly
5daa422aca new test for setting new headers before save (comments #82) 2018-02-27 14:33:26 +01:00
Susann Sgorzaly
48a3cdbc5c new enum for file processing mode. extended documentation (comments #112) 2018-02-27 14:18:00 +01:00
Susann Sgorzaly
fbe5263bca only code improvements 2018-02-27 13:22:11 +01:00
Susann Sgorzaly
5b1002a677 test correction 2018-02-26 10:06:59 +01:00
Susann Sgorzaly
4bbc928f09 added dependency for test 2018-02-26 10:00:52 +01:00
Susann Sgorzaly
951fc68886 new feature: auto detect if parsed file has heading 2018-02-26 09:46:44 +01:00
Susann Sgorzaly
68b849a37b corrected regex to fit all given enclosures. Added test for single enclosure 2018-02-26 08:55:51 +01:00
Susann Sgorzaly
aaefe2a480 introduces new local variable that holds the data 2018-02-26 08:50:35 +01:00
Susann Sgorzaly
611b1a92e8 use strpos instead of preg_match 2018-02-26 08:46:41 +01:00
Susann Sgorzaly
e5eccf1fc1 put tests into new file 2018-02-26 08:38:08 +01:00
Susann Sgorzaly
9e5c97328d renamed function to getTotalDataRowCount 2018-02-26 08:33:09 +01:00
Susann Sgorzaly
b6247c367c reformat code; only extended comment for new function 2018-02-26 08:31:46 +01:00
Susann Sgorzaly
ba4cc0672a reformat code 2018-02-26 06:57:40 +01:00
Susann Sgorzaly
c9cc9697ef new feature: getting total data row count without parsing all data 2018-02-24 16:55:45 +01:00
Susann Sgorzaly
f8fe4cad03 change accessibility of parse_file and parse_string 2018-02-23 10:37:12 +01:00
Susann Sgorzaly
95521cde87 reset file property if input is string 2018-02-23 10:17:09 +01:00
Susann Sgorzaly
249e5a24ac readded missing return statement in parse-function 2018-02-23 10:14:01 +01:00
Susann Sgorzaly
343c683077 corrected test for default sort type. Is set to regular now 2018-02-23 10:02:13 +01:00
Susann Sgorzaly
cf91bf40ff now compatible with old sorting values 2018-02-23 08:11:56 +01:00
Susann Sgorzaly
958af1027e small code improvements 2018-02-22 21:34:39 +01:00
Susann Sgorzaly
7a3120dd28 added test for sort enums (todo: handle exception on test) 2018-02-22 21:15:00 +01:00
Susann Sgorzaly
a74736d4da init implementation of abstract enum class 2018-02-22 21:01:06 +01:00
Susann Sgorzaly
657cec4b4e added enum for sort 2018-02-22 20:41:03 +01:00
35 changed files with 1734 additions and 507 deletions

View File

@@ -13,3 +13,6 @@ insert_final_newline = true
[composer.json] [composer.json]
indent_size = 4 indent_size = 4
[.travis.yml]
indent_size = 2

View File

@@ -1,19 +1,27 @@
dist: trusty
language: php language: php
dist: trusty
php: php:
- 7.2 - '7.4'
- 7.1 - '7.3'
- 7.0 - '7.2'
- 5.6 - '7.1'
- 5.5 - '7.0'
- 5.4 - '5.6'
- '5.5'
before_install:
- composer update
script: script:
- phpunit --version - composer validate
- phpunit --configuration tests/phpunit.xml - vendor/bin/phpunit --version
- vendor/bin/phpunit --configuration tests/phpunit.xml
notifications: notifications:
email: email:
- will.knauss@gmail.com recipients:
on_success: never - will.knauss@gmail.com
on_failure: always on_success: never
on_failure: always

View File

@@ -1,272 +1,348 @@
ParseCSV 1.0.0 ParseCSV 1.2.0
----------------------------------- -----------------------------------
Date: 3-March-2018 Date: 07-Jan-2020
- Renamed class from parseCSV to Csv and added name- Breaking changes: none
space "ParseCsv" for PSR compliance.
New features:
- Added support for MS Excel's "sep=" to detect the - Compatible with PHP 7.4. Thanks to @andreybolonin
delimiter (Issue #60). @morrislaptop @martijnengler and @fjf2002.
- unparse() now also understands $use_mb_convert_encoding.
- Added data type detection - function getDatatypes() - Verbal condition operators are now allowed to contain
guesses the type of each column. upper case letters, for example:
$csv->conditions = 'rating IS GREATER THAN 4';
- MIME: output() sends correct MIME type to browser
if the separator is a tab tab (Issue #79). Bug fixes:
- All filter condition operators containing "is" or "equals"
- Added support for mb_convert_encoding() instead of were broken.
iconv() - see issue #109.
Code quality:
- A number of minor bug fixes - see GitHub issues. - Improved test coverage.
-----------------------------------
- Added many more unit tests.
----------------------------------- ParseCSV 1.1.1
-----------------------------------
Date: 2-Feb-2019
parseCSV 0.4.3 beta
----------------------------------- Breaking changes: none
Date: 1-July-2008
New features: none
- Issue #4. Added an option for setting sorting
type behavior when sorting data. Bug fixes:
Simply set $csv->sort_type to "regular", "numeric", - Function load_data: check length of input, prevents E_NOTICE
or "string". if too long.
- Fixed bugs in unparse().
- Issue #6. Raw loaded file data is now cleared from
file_data property when it has been successfully Code quality:
parsed to keep parseCSV's memory footprint to a - Improved test coverage.
minimum. Specifically handy when using multiple -----------------------------------
instances of parseCSV to process large files.
----------------------------------- ParseCSV 1.1.0
-----------------------------------
Date: 9-Aug-2018
parseCSV 0.4.2 beta
----------------------------------- Breaking changes:
Date: 31-May-2008 - Ignore entirely empty lines at the end of files
See https://github.com/parsecsv/parsecsv-for-php/pull/142
- IMPORTANT! If you're using the output(), - Dropped support for PHP 5.4. Now, you need at leas PHP 5.5.
method please note that the first parameter - Fixed parse()'s return value: return true only if $data is useful.
has been completely removed as it was
technically just useless. Instead, the second New features:
parameter (filename) doubles as its replacement. - Added support for Laravel-style collections via the
Simply put, if filename is not set or null, the new getCollection() function - see
output() method will not output a downloadable https://github.com/parsecsv/parsecsv-for-php/pull/134
file. Please update your existing code - New function getTotalDataRowCount() - useful if
when using 0.4.2 and later :) $limit is set - see pull request #122.
- Added requires to keep Composer-free environments working.
- Small fix to the headers sent by the output()
method. Bug fixes:
- Better support for streams.
- Added a download example using the output() See https://github.com/parsecsv/parsecsv-for-php/pull/147
method to the examples folder. - Fixed output() with custom header.
See https://github.com/parsecsv/parsecsv-for-php/issues/132
----------------------------------- - Fixed bug on _validate_fields_for_unparse() if titles property
is used instead of fields parameter for changing the titles for
unparsing.
parseCSV 0.4.1 beta - Fixed bug in unparse() that caused incorrect column order
----------------------------------- (Issue #41).
Date: 29-May-2008
- Fixed a small bug in how the output() method Code quality:
handles input data. - Improved test coverage.
-----------------------------------
-----------------------------------
ParseCSV 1.0.0
parseCSV 0.4 beta -----------------------------------
----------------------------------- Date: 3-March-2018
Date: 11-Apr-2008
- Renamed class from parseCSV to Csv and added name-
- Error reporting for files/data which is corrupt space "ParseCsv" for PSR compliance.
or has formatting errors like using double
quotes in a field without enclosing quotes. Or - Added support for MS Excel's "sep=" to detect the
not escaping double quotes with a second one. delimiter (Issue #60).
- parse() method does not require input anymore - Added data type detection - function getDatatypes()
if the "$object->file" property has been set. guesses the type of each column.
I'm calling this a beta release due to the heavy - MIME: output() sends correct MIME type to browser
modifications to the core parsing logic required if the separator is a tab char (Issue #79).
for error reporting to work. I have tested the
new code quite extensively, I'm fairly confident - Added support for mb_convert_encoding() instead of
that it still parses exactly as it always has. iconv() - see issue #109.
The second reason I'm calling it a beta release - A number of minor bug fixes - see GitHub issues.
is cause I'm sure the error reporting code will
need more refinements and tweaks to detect more - Added many more unit tests.
types of errors, as it's only picking two types
or syntax errors right now. However, it seems -----------------------------------
these two are the most common errors that you
would be likely to come across.
parseCSV 0.4.3 beta
----------------------------------- -----------------------------------
Date: 1-July-2008
parseCSV 0.3.2 - Issue #4. Added an option for setting sorting
----------------------------------- type behavior when sorting data.
Date: 1-Apr-2008 Simply set $csv->sort_type to "regular", "numeric",
or "string".
This is primarily a bug-fix release for a critical
bug which was brought to my attention. - Issue #6. Raw loaded file data is now cleared from
file_data property when it has been successfully
- Fixed a critical bug in conditions parsing which parsed to keep parseCSV's memory footprint to a
would generate corrupt matching patterns causing minimum. Specifically handy when using multiple
the condition(s) to not work at all in some instances of parseCSV to process large files.
situations.
-----------------------------------
- Fixed a small code error which would cause PHP to
generate a invalid offset notice when zero length
values were fed into the unparse() method to parseCSV 0.4.2 beta
generate CSV data from an array. -----------------------------------
Date: 31-May-2008
Notice: If you have been using the "parsecsv-stable"
branch as an external in any of your projects, - IMPORTANT! If you're using the output(),
please use the "stable/parsecsv" branch from this method please note that the first parameter
point on as I will eventually remove the former due has been completely removed as it was
to it's stupid naming. technically just useless. Instead, the second
parameter (filename) doubles as its replacement.
----------------------------------- Simply put, if filename is not set or null, the
output() method will not output a downloadable
file. Please update your existing code
parseCSV 0.3.1 when using 0.4.2 and later :)
-----------------------------------
Date: 1-Sep-2007 - Small fix to the headers sent by the output()
method.
- Small change to default output settings to
conform with RFC 4180 (http://rfc.net/rfc4180.html). - Added a download example using the output()
Only the LF (line feed) character was used method to the examples folder.
by default to separate rows, rather than
CRLF (carriage return & line feed). -----------------------------------
-----------------------------------
parseCSV 0.4.1 beta
-----------------------------------
parseCSV 0.3.0 Date: 29-May-2008
-----------------------------------
Date: 9-Aug-2007 - Fixed a small bug in how the output() method
handles input data.
- Changed to the MIT license.
-----------------------------------
- Added offset and limit options.
- Added SQL-like conditions for quickly parseCSV 0.4 beta
filtering out entries. Documentation on the -----------------------------------
condition syntax is forthcoming. Date: 11-Apr-2008
- Small parsing modification to comply - Error reporting for files/data which is corrupt
with some recent changes to the specifications or has formatting errors like using double
outlined on Wikipedia's Comma-separated values quotes in a field without enclosing quotes. Or
article. not escaping double quotes with a second one.
- Minor changes and optimizations, and a few - parse() method does not require input anymore
spelling corrections. Oops :) if the "$object->file" property has been set.
- Included more complex code examples in the I'm calling this a beta release due to the heavy
parseCSV download. modifications to the core parsing logic required
for error reporting to work. I have tested the
----------------------------------- new code quite extensively, I'm fairly confident
that it still parses exactly as it always has.
parseCSV 0.2.1 The second reason I'm calling it a beta release
----------------------------------- is cause I'm sure the error reporting code will
Date: 8-Aug-2007 need more refinements and tweaks to detect more
types of errors, as it's only picking two types
- Fixed stupid code which caused auto function or syntax errors right now. However, it seems
to not work in some situations. these two are the most common errors that you
would be likely to come across.
-----------------------------------
-----------------------------------
parseCSV 0.2.0 beta
----------------------------------- parseCSV 0.3.2
Date: 2-Jan-2007 -----------------------------------
Date: 1-Apr-2008
- Added auto() function to automatically detect
delimiter character. This is primarily a bug-fix release for a critical
Useful for user upload in case delimiter is bug which was brought to my attention.
comma (,), tab, or semi-colon (;). Some
versions of MS Excel for Windows use - Fixed a critical bug in conditions parsing which
semi-colons instead of commas when saving to would generate corrupt matching patterns causing
CSV files. the condition(s) to not work at all in some
It uses a process of elimination to eliminate situations.
characters that can not be the delimiter,
so it should work on all CSV-structured files - Fixed a small code error which would cause PHP to
almost no matter what the delimiter is. generate a invalid offset notice when zero length
values were fed into the unparse() method to
- Generally updated some of the core workings generate CSV data from an array.
to increase performance, and offer better
support for large (1MB and up) files. Notice: If you have been using the "parsecsv-stable"
branch as an external in any of your projects,
- Added code examples to header comment. please use the "stable/parsecsv" branch from this
point on as I will eventually remove the former due
----------------------------------- to it's stupid naming.
-----------------------------------
parseCSV 0.1.6 beta
-----------------------------------
Date: 22-Dec-2006 parseCSV 0.3.1
-----------------------------------
- Updated output() function. Date: 1-Sep-2007
----------------------------------- - Small change to default output settings to
conform with RFC 4180 (http://rfc.net/rfc4180.html).
Only the LF (line feed) character was used
parseCSV 0.1.5 beta by default to separate rows, rather than
----------------------------------- CRLF (carriage return & line feed).
Date: 22-Dec-2006
-----------------------------------
- Added output() function for easy output to
browser, for downloading features for example.
parseCSV 0.3.0
----------------------------------- -----------------------------------
Date: 9-Aug-2007
parseCSV 0.1.4 beta - Changed to the MIT license.
-----------------------------------
Date: 17-Dec-2006 - Added offset and limit options.
- Minor changes and fixes - Added SQL-like conditions for quickly
filtering out entries. Documentation on the
----------------------------------- condition syntax is forthcoming.
- Small parsing modification to comply
parseCSV 0.1.3 beta with some recent changes to the specifications
----------------------------------- outlined on Wikipedia's Comma-separated values
Date: 17-Dec-2006 article.
- Added GPL v2.0 license. - Minor changes and optimizations, and a few
spelling corrections. Oops :)
-----------------------------------
- Included more complex code examples in the
parseCSV download.
parseCSV 0.1.2 beta
----------------------------------- -----------------------------------
Date: 17-Dec-2006
- Added encoding() function for easier character parseCSV 0.2.1
encoding configuration. -----------------------------------
Date: 8-Aug-2007
-----------------------------------
- Fixed stupid code which caused auto function
to not work in some situations.
parseCSV 0.1.1 beta
----------------------------------- -----------------------------------
Date: 24-Nov-2006
- Added support for a PHP die command on first parseCSV 0.2.0 beta
line of csv files if they have a .php extension -----------------------------------
to protect secure data from being displayed Date: 2-Jan-2007
directly to the browser.
- Added auto() function to automatically detect
----------------------------------- delimiter character.
Useful for user upload in case delimiter is
comma (,), tab, or semi-colon (;). Some
parseCSV 0.1 beta versions of MS Excel for Windows use
----------------------------------- semi-colons instead of commas when saving to
Date: 23-Nov-2006 CSV files.
It uses a process of elimination to eliminate
- Initial release characters that can not be the delimiter,
so it should work on all CSV-structured files
----------------------------------- almost no matter what the delimiter is.
- Generally updated some of the core workings
to increase performance, and offer better
support for large (1MB and up) files.
- Added code examples to header comment.
-----------------------------------
parseCSV 0.1.6 beta
-----------------------------------
Date: 22-Dec-2006
- Updated output() function.
-----------------------------------
parseCSV 0.1.5 beta
-----------------------------------
Date: 22-Dec-2006
- Added output() function for easy output to
browser, for downloading features for example.
-----------------------------------
parseCSV 0.1.4 beta
-----------------------------------
Date: 17-Dec-2006
- Minor changes and fixes
-----------------------------------
parseCSV 0.1.3 beta
-----------------------------------
Date: 17-Dec-2006
- Added GPL v2.0 license.
-----------------------------------
parseCSV 0.1.2 beta
-----------------------------------
Date: 17-Dec-2006
- Added encoding() function for easier character
encoding configuration.
-----------------------------------
parseCSV 0.1.1 beta
-----------------------------------
Date: 24-Nov-2006
- Added support for a PHP die command on first
line of csv files if they have a .php extension
to protect secure data from being displayed
directly to the browser.
-----------------------------------
parseCSV 0.1 beta
-----------------------------------
Date: 23-Nov-2006
- Initial release
-----------------------------------

View File

@@ -1,4 +1,5 @@
# ParseCsv # ParseCsv
[![Financial Contributors on Open Collective](https://opencollective.com/parsecsv/all/badge.svg?label=financial+contributors)](https://opencollective.com/parsecsv)
ParseCsv is an easy-to-use PHP class that reads and writes CSV data properly. It ParseCsv is an easy-to-use PHP class that reads and writes CSV data properly. It
fully conforms to the specifications outlined on the on the fully conforms to the specifications outlined on the on the
@@ -26,7 +27,7 @@ and third-party support for handling CSV data in PHP.
how different programs like Excel for example outputs CSV data. how different programs like Excel for example outputs CSV data.
* Support for character encoding conversion using PHP's * Support for character encoding conversion using PHP's
`iconv()` and `mb_convert_encoding()` functions. `iconv()` and `mb_convert_encoding()` functions.
* Supports PHP 5.4 and higher. * Supports PHP 5.5 and higher.
It certainly works with PHP 7.2 and all versions in between. It certainly works with PHP 7.2 and all versions in between.
## Installation ## Installation
@@ -77,8 +78,43 @@ $csv->auto('data.csv');
print_r($csv->data); print_r($csv->data);
``` ```
**Parse data with offset**
* ignoring the first X (e.g. two) rows
```php
$csv = new ParseCsv\Csv();
$csv->offset = 2;
$csv->parse('data.csv');
print_r($csv->data);
```
**Limit the number of returned data rows**
```php
$csv = new ParseCsv\Csv();
$csv->limit = 5;
$csv->parse('data.csv');
print_r($csv->data);
```
**Get total number of data rows without parsing whole data**
* Excluding heading line if present (see $csv->header property)
```php
$csv = new ParseCsv\Csv();
$csv->load_data('data.csv');
$count = $csv->getTotalDataRowCount();
print_r($count);
```
**Get most common data type for each column (Requires PHP >= 5.5)**
```php
$csv = new ParseCsv\Csv('data.csv');
$csv->getDatatypes()
print_r($csv->data_types);
```
**Modify data in a CSV file** **Modify data in a CSV file**
Change data values:
```php ```php
$csv = new ParseCsv\Csv(); $csv = new ParseCsv\Csv();
$csv->sort_by = 'id'; $csv->sort_by = 'id';
@@ -88,6 +124,14 @@ $csv->data[4] = array('firstname' => 'John', 'lastname' => 'Doe', 'email' => 'jo
$csv->save(); $csv->save();
``` ```
Enclose each data value by quotes:
```php
$csv = new ParseCsv\Csv();
$csv->parse('data.csv');
$csv->enclose_all = true;
$csv->save();
```
**Replace field names or set ones if missing** **Replace field names or set ones if missing**
```php ```php
@@ -132,6 +176,36 @@ Please find a complete list on the project's [contributors][] page.
## Contributors
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
<a href="https://github.com/parsecsv/parsecsv-for-php/graphs/contributors"><img src="https://opencollective.com/parsecsv/contributors.svg?width=890&button=false" /></a>
### Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/parsecsv/contribute)]
#### Individuals
<a href="https://opencollective.com/parsecsv"><img src="https://opencollective.com/parsecsv/individuals.svg?width=890"></a>
#### Organizations
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/parsecsv/contribute)]
<a href="https://opencollective.com/parsecsv/organization/0/website"><img src="https://opencollective.com/parsecsv/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/parsecsv/organization/1/website"><img src="https://opencollective.com/parsecsv/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/parsecsv/organization/2/website"><img src="https://opencollective.com/parsecsv/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/parsecsv/organization/3/website"><img src="https://opencollective.com/parsecsv/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/parsecsv/organization/4/website"><img src="https://opencollective.com/parsecsv/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/parsecsv/organization/5/website"><img src="https://opencollective.com/parsecsv/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/parsecsv/organization/6/website"><img src="https://opencollective.com/parsecsv/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/parsecsv/organization/7/website"><img src="https://opencollective.com/parsecsv/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/parsecsv/organization/8/website"><img src="https://opencollective.com/parsecsv/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/parsecsv/organization/9/website"><img src="https://opencollective.com/parsecsv/organization/9/avatar.svg"></a>
## License ## License
(The MIT license) (The MIT license)

View File

@@ -10,21 +10,42 @@
{ {
"name": "William Knauss", "name": "William Knauss",
"email": "will.knauss@gmail.com" "email": "will.knauss@gmail.com"
},
{
"name": "Susann Sgorzaly",
"homepage": "https://github.com/susgo"
},
{
"name": "Christian Bläul",
"homepage": "https://github.com/Fonata"
} }
], ],
"autoload":{ "autoload": {
"psr-4":{ "psr-4": {
"ParseCsv\\": "src", "ParseCsv\\": "src"
"ParseCsv\\extensions\\": "src\\extensions", }
},
"autoload-dev": {
"psr-4": {
"ParseCsv\\tests\\": "tests" "ParseCsv\\tests\\": "tests"
} }
}, },
"autoload-dev":{ "require": {
"psr-4":{ "php": ">=5.5"
"ParseCsv\\tests\\": "tests"
}
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "4.1.*" "phpunit/phpunit": "4.1.*"
},
"suggest": {
"illuminate/support": "Fluent array interface for map functions"
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"support": {
"issues": "https://github.com/parsecsv/parsecsv-for-php/issues",
"source": "https://github.com/parsecsv/parsecsv-for-php"
} }
} }

View File

@@ -3,6 +3,7 @@
# include parseCSV class. # include parseCSV class.
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
use ParseCsv\Csv; use ParseCsv\Csv;
@@ -40,7 +41,7 @@ $csv->auto('_books.csv');
background-color: #FFF; background-color: #FFF;
} }
</style> </style>
<table border="0" cellspacing="1" cellpadding="3"> <table>
<tr> <tr>
<?php foreach ($csv->titles as $value): ?> <?php foreach ($csv->titles as $value): ?>
<th><?php echo $value; ?></th> <th><?php echo $value; ?></th>

View File

@@ -4,6 +4,7 @@
# include parseCSV class. # include parseCSV class.
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
use ParseCsv\Csv; use ParseCsv\Csv;
@@ -16,6 +17,7 @@ $csv = new Csv();
$csv->conditions = 'author does not contain dan brown'; $csv->conditions = 'author does not contain dan brown';
// $csv->conditions = 'rating < 4 OR author is John Twelve Hawks'; // $csv->conditions = 'rating < 4 OR author is John Twelve Hawks';
// $csv->conditions = 'rating > 4 AND author is Dan Brown'; // $csv->conditions = 'rating > 4 AND author is Dan Brown';
// $csv->conditions = 'rating is greater than 4';
# Parse '_books.csv' using automatic delimiter detection. # Parse '_books.csv' using automatic delimiter detection.
@@ -41,7 +43,7 @@ $csv->auto('_books.csv');
background-color: #FFF; background-color: #FFF;
} }
</style> </style>
<table border="0" cellspacing="1" cellpadding="3"> <table>
<tr> <tr>
<?php foreach ($csv->titles as $value): ?> <?php foreach ($csv->titles as $value): ?>
<th><?php echo $value; ?></th> <th><?php echo $value; ?></th>

View File

@@ -3,6 +3,7 @@
# include parseCSV class. # include parseCSV class.
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
use ParseCsv\Csv; use ParseCsv\Csv;
@@ -31,5 +32,3 @@ $csv->output('books.csv');
# data to download the output as a CSV file. if it's not set # data to download the output as a CSV file. if it's not set
# or is set to null, output will only return the generated CSV # or is set to null, output will only return the generated CSV
# output data, and will not output to the browser itself. # output data, and will not output to the browser itself.
?>

View File

@@ -4,6 +4,7 @@
# include parseCSV class. # include parseCSV class.
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
use ParseCsv\Csv; use ParseCsv\Csv;
@@ -54,7 +55,7 @@ $csv->auto('_books.csv');
background-color: #FFF; background-color: #FFF;
} }
</style> </style>
<table border="0" cellspacing="1" cellpadding="3"> <table>
<tr> <tr>
<?php foreach ($csv->titles as $value): ?> <?php foreach ($csv->titles as $value): ?>
<th><?php echo $value; ?></th> <th><?php echo $value; ?></th>

View File

@@ -0,0 +1,27 @@
<?php
# include parseCSV class.
require __DIR__ . '/../vendor/autoload.php';
use ParseCsv\Csv;
# Create new parseCSV object.
$csv = new Csv();
# When saving, don't write the header row:
$csv->heading = false;
# Specify which columns to write, and in which order.
# We won't output the 'Awesome' column this time.
$csv->titles = ['Age', 'Name'];
# Data to write:
$csv->data = [
0 => ['Name' => 'Anne', 'Age' => 45, 'Awesome' => true],
1 => ['Name' => 'John', 'Age' => 44, 'Awesome' => false],
];
# Then we save the file to the file system:
$csv->save('people.csv');

View File

@@ -6,6 +6,10 @@
// Check if people used Composer to include this project in theirs // Check if people used Composer to include this project in theirs
if (!file_exists(__DIR__ . '/vendor/autoload.php')) { if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
require __DIR__ . '/src/enums/AbstractEnum.php';
require __DIR__ . '/src/enums/DatatypeEnum.php';
require __DIR__ . '/src/enums/FileProcessingModeEnum.php';
require __DIR__ . '/src/enums/SortEnum.php';
require __DIR__ . '/src/extensions/DatatypeTrait.php'; require __DIR__ . '/src/extensions/DatatypeTrait.php';
require __DIR__ . '/src/Csv.php'; require __DIR__ . '/src/Csv.php';
} else { } else {

View File

@@ -2,12 +2,14 @@
namespace ParseCsv; namespace ParseCsv;
use Illuminate\Support\Collection;
use ParseCsv\enums\FileProcessingModeEnum;
use ParseCsv\enums\SortEnum;
use ParseCsv\extensions\DatatypeTrait; use ParseCsv\extensions\DatatypeTrait;
class Csv { class Csv {
/* /*
Class: ParseCSV 1.0.0
https://github.com/parsecsv/parsecsv-for-php https://github.com/parsecsv/parsecsv-for-php
Fully conforms to the specifications lined out on Wikipedia: Fully conforms to the specifications lined out on Wikipedia:
@@ -16,7 +18,6 @@ class Csv {
Based on the concept of Ming Hong Ng's CsvFileParser class: Based on the concept of Ming Hong Ng's CsvFileParser class:
- http://minghong.blogspot.com/2006/07/csv-parser-for-php.html - http://minghong.blogspot.com/2006/07/csv-parser-for-php.html
(The MIT license) (The MIT license)
Copyright (c) 2014 Jim Myhrberg. Copyright (c) 2014 Jim Myhrberg.
@@ -89,7 +90,7 @@ class Csv {
* *
* @var string|null * @var string|null
*/ */
public $sort_type = null; public $sort_type = SortEnum::SORT_TYPE_REGULAR;
/** /**
* Delimiter * Delimiter
@@ -125,7 +126,9 @@ class Csv {
/** /**
* Offset * Offset
* Number of rows to ignore from beginning of data * Number of rows to ignore from beginning of data. If present, the heading
* row is also counted (if $this->heading == true). In other words,
* $offset == 1 and $offset == 0 have the same meaning in that situation.
* *
* @var int|null * @var int|null
*/ */
@@ -148,7 +151,7 @@ class Csv {
public $auto_depth = 15; public $auto_depth = 15;
/** /**
* Auto Non Charts * Auto Non Chars
* Characters that should be ignored when attempting to auto-detect delimiter * Characters that should be ignored when attempting to auto-detect delimiter
* *
* @var string * @var string
@@ -166,7 +169,7 @@ class Csv {
/** /**
* Convert Encoding * Convert Encoding
* Should we convert the csv encoding? * Should we convert the CSV character encoding?
* *
* @var bool * @var bool
*/ */
@@ -275,8 +278,20 @@ class Csv {
public $error_info = array(); public $error_info = array();
/** /**
* Titles * $titles has 4 distinct tasks:
* CSV titles if they exists * 1. After reading in CSV data, $titles will contain the column headers
* present in the data.
*
* 2. It defines which fields from the $data array to write e.g. when
* calling unparse(), and in which order. This lets you skip columns you
* don't want in your output, but are present in $data.
* See examples/save_to_file_without_header_row.php.
*
* 3. It lets you rename columns. See StreamTest::testWriteStream for an
* example.
*
* 4. When writing data and $header is true, then $titles is also used for
* the first row.
* *
* @var array * @var array
*/ */
@@ -284,7 +299,7 @@ class Csv {
/** /**
* Data * Data
* Two dimensional array of CSV data * Two-dimensional array of CSV data
* *
* @var array * @var array
*/ */
@@ -296,13 +311,37 @@ class Csv {
* Constructor * Constructor
* Class constructor * Class constructor
* *
* @param string|null $input The CSV string or a direct filepath * @param string|null $input The CSV string or a direct file path
* @param integer|null $offset Number of rows to ignore from the beginning of the data * @param integer|null $offset Number of rows to ignore from the
* @param integer|null $limit Limits the number of returned rows to specified amount * beginning of the data
* @param string|null $conditions Basic SQL-like conditions for row matching * @param integer|null $limit Limits the number of returned rows
* @param null|true $keep_file_data Keep raw file data in memory after successful parsing (useful for debugging) * to specified amount
* @param string|null $conditions Basic SQL-like conditions for row
* matching
* @param null|true $keep_file_data Keep raw file data in memory after
* successful parsing
* (useful for debugging)
*/ */
public function __construct($input = null, $offset = null, $limit = null, $conditions = null, $keep_file_data = null) { public function __construct($input = null, $offset = null, $limit = null, $conditions = null, $keep_file_data = null) {
$this->init($offset, $limit, $conditions, $keep_file_data);
if (!empty($input)) {
$this->parse($input);
}
}
/**
* @param integer|null $offset Number of rows to ignore from the
* beginning of the data
* @param integer|null $limit Limits the number of returned rows
* to specified amount
* @param string|null $conditions Basic SQL-like conditions for row
* matching
* @param null|true $keep_file_data Keep raw file data in memory after
* successful parsing
* (useful for debugging)
*/
public function init($offset = null, $limit = null, $conditions = null, $keep_file_data = null) {
if (!is_null($offset)) { if (!is_null($offset)) {
$this->offset = $offset; $this->offset = $offset;
} }
@@ -318,10 +357,6 @@ class Csv {
if (!is_null($keep_file_data)) { if (!is_null($keep_file_data)) {
$this->keep_file_data = $keep_file_data; $this->keep_file_data = $keep_file_data;
} }
if (!empty($input)) {
$this->parse($input);
}
} }
// ============================================== // ==============================================
@@ -332,10 +367,13 @@ class Csv {
* Parse * Parse
* Parse a CSV file or string * Parse a CSV file or string
* *
* @param string|null $input The CSV string or a direct filepath * @param string|null $input The CSV string or a direct file path
* @param integer $offset Number of rows to ignore from the beginning of the data * @param integer $offset Number of rows to ignore from the
* @param integer $limit Limits the number of returned rows to specified amount * beginning of the data
* @param string $conditions Basic SQL-like conditions for row matching * @param integer $limit Limits the number of returned rows to
* specified amount
* @param string $conditions Basic SQL-like conditions for row
* matching
* *
* @return bool True on success * @return bool True on success
*/ */
@@ -344,51 +382,44 @@ class Csv {
$input = $this->file; $input = $this->file;
} }
if (!empty($input)) { if (empty($input)) {
if (!is_null($offset)) { return false;
$this->offset = $offset;
}
if (!is_null($limit)) {
$this->limit = $limit;
}
if (!is_null($conditions)) {
$this->conditions = $conditions;
}
if (strlen($input) <= PHP_MAXPATHLEN && is_readable($input)) {
$this->data = $this->parse_file($input);
} else {
$this->file_data = &$input;
$this->data = $this->parse_string();
}
if ($this->data === false) {
return false;
}
} }
return true; $this->init($offset, $limit, $conditions);
if (strlen($input) <= PHP_MAXPATHLEN && is_readable($input)) {
$this->file = $input;
$this->data = $this->_parse_file();
} else {
$this->file = null;
$this->file_data = &$input;
$this->data = $this->_parse_string();
}
return $this->data !== false;
} }
/** /**
* Save * Save
* Save changes, or write a new file and/or data * Save changes, or write a new file and/or data
* *
* @param string $file File location to save to * @param string $file File location to save to
* @param array $data 2D array of data * @param array $data 2D array of data
* @param bool $append Append current data to end of target CSV, if file exists * @param bool $append Append current data to end of target CSV, if file
* @param array $fields Field names * exists
* @param array $fields Field names. Sets the header. If it is not set
* $this->titles would be used instead.
* *
* @return bool * @return bool
* True on success
*/ */
public function save($file = '', $data = array(), $append = false, $fields = array()) { public function save($file = '', $data = array(), $append = FileProcessingModeEnum::MODE_FILE_OVERWRITE, $fields = array()) {
if (empty($file)) { if (empty($file)) {
$file = &$this->file; $file = &$this->file;
} }
$mode = $append ? 'ab' : 'wb'; $mode = FileProcessingModeEnum::getAppendMode($append);
$is_php = preg_match('/\.php$/i', $file) ? true : false; $is_php = preg_match('/\.php$/i', $file) ? true : false;
return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode); return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode);
@@ -398,13 +429,16 @@ class Csv {
* Output * Output
* Generate a CSV based string for output. * Generate a CSV based string for output.
* *
* @param string|null $filename If a filename is specified here or in the * @param string|null $filename If a filename is specified here or in the
* object, headers and data will be output * object, headers and data will be output
* directly to browser as a downloadable * directly to browser as a downloadable
* file. * file. This file doesn't have to exist on
* @param array[] $data 2D array with data * the server; the parameter only affects
* @param array $fields Field names * how the download is called to the
* @param string|null $delimiter character used to separate data * browser.
* @param array[] $data 2D array with data
* @param array $fields Field names
* @param string|null $delimiter character used to separate data
* *
* @return string The resulting CSV string * @return string The resulting CSV string
*/ */
@@ -440,8 +474,8 @@ class Csv {
* Encoding * Encoding
* Convert character encoding * Convert character encoding
* *
* @param string $input Input character encoding, uses default if left blank * @param string $input Input character encoding, uses default if left blank
* @param string $output Output character encoding, uses default if left blank * @param string $output Output character encoding, uses default if left blank
*/ */
public function encoding($input = null, $output = null) { public function encoding($input = null, $output = null) {
$this->convert_encoding = true; $this->convert_encoding = true;
@@ -459,11 +493,11 @@ class Csv {
* Auto-Detect Delimiter: Find delimiter by analyzing a specific number of * Auto-Detect Delimiter: Find delimiter by analyzing a specific number of
* rows to determine most probable delimiter character * rows to determine most probable delimiter character
* *
* @param string|null $file Local CSV file * @param string|null $file Local CSV file
* @param bool $parse True/false parse file directly * @param bool $parse True/false parse file directly
* @param int $search_depth Number of rows to analyze * @param int $search_depth Number of rows to analyze
* @param string $preferred Preferred delimiter characters * @param string $preferred Preferred delimiter characters
* @param string|null $enclosure Enclosure character, default is double quote ("). * @param string|null $enclosure Enclosure character, default is double quote (").
* *
* @return string The detected field delimiter * @return string The detected field delimiter
*/ */
@@ -502,25 +536,63 @@ class Csv {
// parse data // parse data
if ($parse) { if ($parse) {
$this->data = $this->parse_string(); $this->data = $this->_parse_string();
} }
return $this->delimiter; return $this->delimiter;
} }
/**
* Get total number of data rows (exclusive heading line if present) in CSV
* without parsing the whole data string.
*
* @return bool|int
*/
public function getTotalDataRowCount() {
if (empty($this->file_data)) {
return false;
}
$data = $this->file_data;
$this->_detect_and_remove_sep_row_from_data($data);
$pattern = sprintf('/%1$s[^%1$s]*%1$s/i', $this->enclosure);
preg_match_all($pattern, $data, $matches);
/** @var array[] $matches */
foreach ($matches[0] as $match) {
if (empty($match) || (strpos($match, $this->enclosure) === false)) {
continue;
}
$replace = str_replace(["\r", "\n"], '', $match);
$data = str_replace($match, $replace, $data);
}
$headingRow = $this->heading ? 1 : 0;
$count = substr_count($data, "\r")
+ substr_count($data, "\n")
- substr_count($data, "\r\n")
- $headingRow;
return $count;
}
// ============================================== // ==============================================
// ----- [ Core Functions ] --------------------- // ----- [ Core Functions ] ---------------------
// ============================================== // ==============================================
/** /**
* Parse File * Parse File
* Read file to string and call parse_string() * Read file to string and call _parse_string()
* *
* @param string|null $file Local CSV file * @param string|null $file Local CSV file
* *
* @return array|bool * @return array|bool
*/ */
public function parse_file($file = null) { protected function _parse_file($file = null) {
if (is_null($file)) { if (is_null($file)) {
$file = $this->file; $file = $this->file;
} }
@@ -529,13 +601,15 @@ class Csv {
$this->load_data($file); $this->load_data($file);
} }
return !empty($this->file_data) ? $this->parse_string() : false; return !empty($this->file_data) ? $this->_parse_string() : false;
} }
/** /**
* Parse CSV strings to arrays. If you need BOM detection or character * Internal function to parse CSV strings to arrays.
* encoding conversion, please call load_data() first, followed by a call to *
* parse_string() with no parameters. * If you need BOM detection or character encoding conversion, please call
* $csv->load_data($your_data_string) first, followed by a call to
* $csv->parse($csv->file_data).
* *
* To detect field separators, please use auto() instead. * To detect field separators, please use auto() instead.
* *
@@ -543,7 +617,7 @@ class Csv {
* *
* @return array|false - 2D array with CSV data, or false on failure * @return array|false - 2D array with CSV data, or false on failure
*/ */
public function parse_string($data = null) { protected function _parse_string($data = null) {
if (empty($data)) { if (empty($data)) {
if ($this->_check_data()) { if ($this->_check_data()) {
$data = &$this->file_data; $data = &$this->file_data;
@@ -566,7 +640,7 @@ class Csv {
// force the parser to process end of data as a character (false) when // force the parser to process end of data as a character (false) when
// data does not end with a line feed or carriage return character. // data does not end with a line feed or carriage return character.
$lch = $data{$strlen - 1}; $lch = $data[$strlen - 1];
if ($lch != "\n" && $lch != "\r") { if ($lch != "\n" && $lch != "\r") {
$data .= "\n"; $data .= "\n";
$strlen++; $strlen++;
@@ -574,8 +648,8 @@ class Csv {
// walk through each character // walk through each character
for ($i = 0; $i < $strlen; $i++) { for ($i = 0; $i < $strlen; $i++) {
$ch = isset($data{$i}) ? $data{$i} : false; $ch = isset($data[$i]) ? $data[$i] : false;
$nch = isset($data{$i + 1}) ? $data{$i + 1} : false; $nch = isset($data[$i + 1]) ? $data[$i + 1] : false;
// open/close quotes, and inline quotes // open/close quotes, and inline quotes
if ($ch == $this->enclosure) { if ($ch == $this->enclosure) {
@@ -605,10 +679,10 @@ class Csv {
$i++; $i++;
} elseif ($nch != $this->delimiter && $nch != "\r" && $nch != "\n") { } elseif ($nch != $this->delimiter && $nch != "\r" && $nch != "\n") {
$x = $i + 1; $x = $i + 1;
while (isset($data{$x}) && ltrim($data{$x}, $white_spaces) == '') { while (isset($data[$x]) && ltrim($data[$x], $white_spaces) == '') {
$x++; $x++;
} }
if ($data{$x} == $this->delimiter) { if ($data[$x] == $this->delimiter) {
$enclosed = false; $enclosed = false;
$i = $x; $i = $x;
} else { } else {
@@ -648,7 +722,7 @@ class Csv {
$col++; $col++;
// end of row // end of row
if ($ch == "\n" || $ch == "\r" || $ch === false) { if (in_array($ch, ["\n", "\r", false], true)) {
if ($this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions)) { if ($this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions)) {
if ($this->heading && empty($head)) { if ($this->heading && empty($head)) {
$head = $row; $head = $row;
@@ -694,13 +768,7 @@ class Csv {
$this->titles = $head; $this->titles = $head;
if (!empty($this->sort_by)) { if (!empty($this->sort_by)) {
$sort_type = SORT_REGULAR; $sort_type = SortEnum::getSorting($this->sort_type);
if ($this->sort_type == 'numeric') {
$sort_type = SORT_NUMERIC;
} elseif ($this->sort_type == 'string') {
$sort_type = SORT_STRING;
}
$this->sort_reverse ? krsort($rows, $sort_type) : ksort($rows, $sort_type); $this->sort_reverse ? krsort($rows, $sort_type) : ksort($rows, $sort_type);
if ($this->offset !== null || $this->limit !== null) { if ($this->offset !== null || $this->limit !== null) {
@@ -728,9 +796,12 @@ class Csv {
* *
* @return string CSV data * @return string CSV data
*/ */
public function unparse($data = array(), $fields = array(), $append = false, $is_php = false, $delimiter = null) { public function unparse($data = array(), $fields = array(), $append = FileProcessingModeEnum::MODE_FILE_OVERWRITE, $is_php = false, $delimiter = null) {
if (!is_array($data) || empty($data)) { if (!is_array($data) || empty($data)) {
$data = &$this->data; $data = &$this->data;
} else {
/** @noinspection ReferenceMismatchInspection */
$this->data = $data;
} }
if (!is_array($fields) || empty($fields)) { if (!is_array($fields) || empty($fields)) {
@@ -745,8 +816,16 @@ class Csv {
$entry = array(); $entry = array();
// create heading // create heading
/** @noinspection ReferenceMismatchInspection */
$fieldOrder = $this->_validate_fields_for_unparse($fields);
if (!$fieldOrder && !empty($data)) {
$column_count = count($data[0]);
$columns = range(0, $column_count - 1, 1);
$fieldOrder = array_combine($columns, $columns);
}
if ($this->heading && !$append && !empty($fields)) { if ($this->heading && !$append && !empty($fields)) {
foreach ($fields as $key => $column_name) { foreach ($fieldOrder as $column_name) {
$entry[] = $this->_enclose_value($column_name, $delimiter); $entry[] = $this->_enclose_value($column_name, $delimiter);
} }
@@ -756,7 +835,8 @@ class Csv {
// create data // create data
foreach ($data as $key => $row) { foreach ($data as $key => $row) {
foreach ($row as $cell_value) { foreach (array_keys($fieldOrder) as $index) {
$cell_value = $row[$index];
$entry[] = $this->_enclose_value($cell_value, $delimiter); $entry[] = $this->_enclose_value($cell_value, $delimiter);
} }
@@ -765,18 +845,73 @@ class Csv {
} }
if ($this->convert_encoding) { if ($this->convert_encoding) {
$string = iconv($this->input_encoding, $this->output_encoding, $string); /** @noinspection PhpComposerExtensionStubsInspection
*
* If you receive an error at the following 3 lines, you must enable
* the following PHP extension:
*
* - if $use_mb_convert_encoding is true: mbstring
* - if $use_mb_convert_encoding is false: iconv
*/
$string = $this->use_mb_convert_encoding ?
mb_convert_encoding($string, $this->output_encoding, $this->input_encoding) :
iconv($this->input_encoding, $this->output_encoding, $string);
} }
return $string; return $string;
} }
private function _validate_fields_for_unparse($fields) {
if (empty($fields)) {
$fields = $this->titles;
}
if (empty($fields)) {
return array();
}
// this is needed because sometime titles property is overwritten instead of using fields parameter!
$titlesOnParse = !empty($this->data) ? array_keys(reset($this->data)) : array();
// both are identical, also in ordering OR we have no data (only titles)
if (empty($titlesOnParse) || array_values($fields) === array_values($titlesOnParse)) {
return array_combine($fields, $fields);
}
// if renaming given by: $oldName => $newName (maybe with reorder and / or subset):
// todo: this will only work if titles are unique
$fieldOrder = array_intersect(array_flip($fields), $titlesOnParse);
if (!empty($fieldOrder)) {
return array_flip($fieldOrder);
}
$fieldOrder = array_intersect($fields, $titlesOnParse);
if (!empty($fieldOrder)) {
return array_combine($fieldOrder, $fieldOrder);
}
// original titles are not given in fields. that is okay if count is okay.
if (count($fields) != count($titlesOnParse)) {
throw new \UnexpectedValueException(
"The specified fields do not match any titles and do not match column count.\n" .
"\$fields was " . print_r($fields, true) .
"\$titlesOnParse was " . print_r($titlesOnParse, true));
}
return array_combine($titlesOnParse, $fields);
}
/** /**
* Load local file or string * Load local file or string.
* *
* @param string|null $input local CSV file * Only use this function if auto() and parse() don't handle your data well.
* *
* @return true or false * This function load_data() is able to handle BOMs and encodings. The data
* is stored within the $this->file_data class field.
*
* @param string|null $input local CSV file or CSV data as a string
*
* @return bool True on success
*/ */
public function load_data($input = null) { public function load_data($input = null) {
$data = null; $data = null;
@@ -784,9 +919,10 @@ class Csv {
if (is_null($input)) { if (is_null($input)) {
$file = $this->file; $file = $this->file;
} elseif (file_exists($input)) { } elseif (\strlen($input) <= PHP_MAXPATHLEN && file_exists($input)) {
$file = $input; $file = $input;
} else { } else {
// It is CSV data as a string.
$data = $input; $data = $input;
} }
@@ -814,6 +950,14 @@ class Csv {
} }
if ($this->convert_encoding && $this->input_encoding !== $this->output_encoding) { if ($this->convert_encoding && $this->input_encoding !== $this->output_encoding) {
/** @noinspection PhpComposerExtensionStubsInspection
*
* If you receive an error at the following 3 lines, you must enable
* the following PHP extension:
*
* - if $use_mb_convert_encoding is true: mbstring
* - if $use_mb_convert_encoding is false: iconv
*/
$data = $this->use_mb_convert_encoding ? $data = $this->use_mb_convert_encoding ?
mb_convert_encoding($data, $this->output_encoding, $this->input_encoding) : mb_convert_encoding($data, $this->output_encoding, $this->input_encoding) :
iconv($this->input_encoding, $this->output_encoding, $data); iconv($this->input_encoding, $this->output_encoding, $data);
@@ -883,12 +1027,19 @@ class Csv {
*/ */
protected function _validate_row_condition($row, $condition) { protected function _validate_row_condition($row, $condition) {
$operators = array( $operators = array(
'=', 'equals', 'is', '=',
'!=', 'is not', 'equals',
'<', 'is less than', 'is',
'>', 'is greater than', '!=',
'<=', 'is less than or equals', 'is not',
'>=', 'is greater than or equals', '<',
'is less than',
'>',
'is greater than',
'<=',
'is less than or equals',
'>=',
'is greater than or equals',
'contains', 'contains',
'does not contain', 'does not contain',
); );
@@ -903,23 +1054,34 @@ class Csv {
if (preg_match('/^(.+) (' . $operators_regex . ') (.+)$/i', trim($condition), $capture)) { if (preg_match('/^(.+) (' . $operators_regex . ') (.+)$/i', trim($condition), $capture)) {
$field = $capture[1]; $field = $capture[1];
$op = $capture[2]; $op = strtolower($capture[2]);
$value = $capture[3]; $value = $capture[3];
if ($op == 'equals' && preg_match('/^(.+) is (less|greater) than or$/i', $field, $m)) {
$field = $m[1];
$op = strtolower($m[2]) == 'less' ? '<=' : '>=';
}
if ($op == 'is' && preg_match('/^(less|greater) than (.+)$/i', $value, $m)) {
$value = $m[2];
$op = strtolower($m[1]) == 'less' ? '<' : '>';
}
if ($op == 'is' && preg_match('/^not (.+)$/i', $value, $m)) {
$value = $m[1];
$op = '!=';
}
if (preg_match('/^([\'\"]{1})(.*)([\'\"]{1})$/', $value, $capture)) { if (preg_match('/^([\'"])(.*)([\'"])$/', $value, $capture) && $capture[1] == $capture[3]) {
if ($capture[1] == $capture[3]) { $value = strtr($capture[2], array(
$value = strtr($capture[2], array( "\\n" => "\n",
"\\n" => "\n", "\\r" => "\r",
"\\r" => "\r", "\\t" => "\t",
"\\t" => "\t", ));
));
$value = stripslashes($value); $value = stripslashes($value);
}
} }
if (array_key_exists($field, $row)) { if (array_key_exists($field, $row)) {
if (($op == '=' || $op == 'equals' || $op == 'is') && $row[$field] == $value) { $op_equals = in_array($op, ['=', 'equals', 'is'], true);
if ($op_equals && $row[$field] == $value) {
return '1'; return '1';
} elseif (($op == '!=' || $op == 'is not') && $row[$field] != $value) { } elseif (($op == '!=' || $op == 'is not') && $row[$field] != $value) {
return '1'; return '1';
@@ -963,19 +1125,19 @@ class Csv {
* Enclose values if needed * Enclose values if needed
* - only used by unparse() * - only used by unparse()
* *
* @param string $value Cell value to process * @param string|null $value Cell value to process
* @param string $delimiter Character to put between cells on the same row * @param string $delimiter Character to put between cells on the same row
* *
* @return string Processed value * @return string Processed value
*/ */
protected function _enclose_value($value = null, $delimiter) { protected function _enclose_value($value, $delimiter) {
if ($value !== null && $value != '') { if ($value !== null && $value != '') {
$delimiter_quoted = $delimiter ? $delimiter_quoted = $delimiter ?
preg_quote($delimiter, '/') . "|" preg_quote($delimiter, '/') . "|"
: ''; : '';
$enclosure_quoted = preg_quote($this->enclosure, '/'); $enclosure_quoted = preg_quote($this->enclosure, '/');
$pattern = "/" . $delimiter_quoted . $enclosure_quoted . "|\n|\r/i"; $pattern = "/" . $delimiter_quoted . $enclosure_quoted . "|\n|\r/i";
if ($this->enclose_all || preg_match($pattern, $value) || ($value{0} == ' ' || substr($value, -1) == ' ')) { if ($this->enclose_all || preg_match($pattern, $value) || strpos($value, ' ') === 0 || substr($value, -1) == ' ') {
$value = str_replace($this->enclosure, $this->enclosure . $this->enclosure, $value); $value = str_replace($this->enclosure, $this->enclosure . $this->enclosure, $value);
$value = $this->enclosure . $value . $this->enclosure; $value = $this->enclosure . $value . $this->enclosure;
} }
@@ -987,7 +1149,7 @@ class Csv {
/** /**
* Check file data * Check file data
* *
* @param string|null $file local filename * @param string|null $file local filename
* *
* @return bool * @return bool
*/ */
@@ -1007,10 +1169,10 @@ class Csv {
* Check if passed info might be delimiter * Check if passed info might be delimiter
* Only used by find_delimiter * Only used by find_delimiter
* *
* @param string $char Potential field separating character * @param string $char Potential field separating character
* @param array $array Frequency * @param array $array Frequency
* @param int $depth Number of analyzed rows * @param int $depth Number of analyzed rows
* @param string $preferred Preferred delimiter characters * @param string $preferred Preferred delimiter characters
* *
* @return string|false special string used for delimiter selection, or false * @return string|false special string used for delimiter selection, or false
*/ */
@@ -1046,21 +1208,19 @@ class Csv {
} }
/** /**
* Read local file * Read local file.
* *
* @param string|null $file local filename * @param string $file local filename
* *
* @return string|false Data from file, or false on failure * @return string|false Data from file, or false on failure
*/ */
protected function _rfile($file = null) { protected function _rfile($file) {
if (is_readable($file)) { if (is_readable($file)) {
if (!($fh = fopen($file, 'r'))) { $data = file_get_contents($file);
if ($data === false) {
return false; return false;
} }
return rtrim($data, "\r\n");
$data = fread($fh, filesize($file));
fclose($fh);
return $data;
} }
return false; return false;
@@ -1069,12 +1229,14 @@ class Csv {
/** /**
* Write to local file * Write to local file
* *
* @param string $file local filename * @param string $file local filename
* @param string $content data to write to file * @param string $content data to write to file
* @param string $mode fopen() mode * @param string $mode fopen() mode
* @param int $lock flock() mode * @param int $lock flock() mode
*
* @return bool
* True on success
* *
* @return true or false
*/ */
protected function _wfile($file, $content = '', $mode = 'wb', $lock = LOCK_EX) { protected function _wfile($file, $content = '', $mode = 'wb', $lock = LOCK_EX) {
if ($fp = fopen($file, $mode)) { if ($fp = fopen($file, $mode)) {
@@ -1095,7 +1257,7 @@ class Csv {
* first line containing only "sep=;", where the last character is the * first line containing only "sep=;", where the last character is the
* separator. Microsoft Excel is able to open such files. * separator. Microsoft Excel is able to open such files.
* *
* @param string $data    file data * @param string $data file data
* *
* @return string|false detected delimiter, or false if none found * @return string|false detected delimiter, or false if none found
*/ */
@@ -1113,7 +1275,7 @@ class Csv {
/** /**
* Support for Excel-compatible sep=? row. * Support for Excel-compatible sep=? row.
* *
* @param string $data_string    file data to be updated * @param string $data_string file data to be updated
* *
* @return bool TRUE if sep= line was found at the very beginning of the file * @return bool TRUE if sep= line was found at the very beginning of the file
*/ */
@@ -1162,9 +1324,9 @@ class Csv {
// walk specific depth finding possible delimiter characters // walk specific depth finding possible delimiter characters
for ($i = 0; $i < $strlen; $i++) { for ($i = 0; $i < $strlen; $i++) {
$ch = $data{$i}; $ch = $data[$i];
$nch = isset($data{$i + 1}) ? $data{$i + 1} : false; $nch = isset($data[$i + 1]) ? $data[$i + 1] : false;
$pch = isset($data{$i - 1}) ? $data{$i - 1} : false; $pch = isset($data[$i - 1]) ? $data[$i - 1] : false;
// open and closing quotes // open and closing quotes
$is_newline = ($ch == "\n" && $pch != "\r") || $ch == "\r"; $is_newline = ($ch == "\n" && $pch != "\r") || $ch == "\r";
@@ -1209,4 +1371,29 @@ class Csv {
ksort($filtered); ksort($filtered);
$this->delimiter = reset($filtered); $this->delimiter = reset($filtered);
} }
/**
* getCollection
* Returns a Illuminate/Collection object
* This may prove to be helpful to people who want to
* create macros, and or use map functions
*
* @access public
* @link https://laravel.com/docs/5.6/collections
*
* @throws \ErrorException - If the Illuminate\Support\Collection class is not found
*
* @return Collection
*/
public function getCollection() {
//does the Illuminate\Support\Collection class exists?
//this uses the autoloader to try to determine
//@see http://php.net/manual/en/function.class-exists.php
if (class_exists('Illuminate\Support\Collection', true) == false) {
throw new \ErrorException('It would appear you have not installed the illuminate/support package!');
}
//return the collection
return new Collection($this->data);
}
} }

View File

@@ -0,0 +1,38 @@
<?php
namespace ParseCsv\enums;
abstract class AbstractEnum {
/**
* Creates a new value of some type
*
* @param mixed $value
*
* @throws \UnexpectedValueException if incompatible type is given.
*/
public function __construct($value) {
if (!$this->isValid($value)) {
throw new \UnexpectedValueException("Value '$value' is not part of the enum " . get_called_class());
}
$this->value = $value;
}
public static function getConstants() {
$class = get_called_class();
$reflection = new \ReflectionClass($class);
return $reflection->getConstants();
}
/**
* Check if enum value is valid
*
* @param $value
*
* @return bool
*/
public static function isValid($value) {
return in_array($value, static::getConstants(), true);
}
}

View File

@@ -9,7 +9,7 @@ namespace ParseCsv\enums;
* *
* todo: needs a basic parent enum class for error handling. * todo: needs a basic parent enum class for error handling.
*/ */
class DatatypeEnum { class DatatypeEnum extends AbstractEnum {
const __DEFAULT = self::TYPE_STRING; const __DEFAULT = self::TYPE_STRING;

View File

@@ -0,0 +1,28 @@
<?php
namespace ParseCsv\enums;
/**
* Class FileProcessingEnum
*
* @package ParseCsv\enums
*
* todo extends a basic enum class after merging #121
*/
class FileProcessingModeEnum {
const __default = self::MODE_FILE_OVERWRITE;
const MODE_FILE_APPEND = true;
const MODE_FILE_OVERWRITE = false;
public static function getAppendMode($mode) {
if ($mode == self::MODE_FILE_APPEND) {
return 'ab';
}
return 'wb';
}
}

30
src/enums/SortEnum.php Normal file
View File

@@ -0,0 +1,30 @@
<?php
namespace ParseCsv\enums;
class SortEnum extends AbstractEnum {
const __DEFAULT = self::SORT_TYPE_REGULAR;
const SORT_TYPE_REGULAR = 'regular';
const SORT_TYPE_NUMERIC = 'numeric';
const SORT_TYPE_STRING = 'string';
private static $sorting = array(
self::SORT_TYPE_REGULAR => SORT_REGULAR,
self::SORT_TYPE_STRING => SORT_STRING,
self::SORT_TYPE_NUMERIC => SORT_NUMERIC,
);
public static function getSorting($type) {
if (array_key_exists($type, self::$sorting)) {
return self::$sorting[$type];
}
return self::$sorting[self::__DEFAULT];
}
}

View File

@@ -2,13 +2,15 @@
namespace ParseCsv\extensions; namespace ParseCsv\extensions;
use ParseCsv\enums\DatatypeEnum;
trait DatatypeTrait { trait DatatypeTrait {
/** /**
* Datatypes * Data Types
* Datatypes of CSV data-columns * Data types of CSV data-columns, keyed by the column name. Possible values
* are string, float, integer, boolean, date. See DatatypeEnum.
* *
* @access public
* @var array * @var array
*/ */
public $data_types = []; public $data_types = [];
@@ -17,8 +19,6 @@ trait DatatypeTrait {
* Check data type for one column. * Check data type for one column.
* Check for most commonly data type for one column. * Check for most commonly data type for one column.
* *
* @access private
*
* @param array $datatypes * @param array $datatypes
* *
* @return string|false * @return string|false
@@ -45,15 +45,13 @@ trait DatatypeTrait {
* *
* Requires PHP >= 5.5 * Requires PHP >= 5.5
* *
* @access public * @uses DatatypeEnum::getValidTypeFromSample
*
* @uses getDatatypeFromString
* *
* @return array|bool * @return array|bool
*/ */
public function getDatatypes() { public function getDatatypes() {
if (empty($this->data)) { if (empty($this->data)) {
$this->data = $this->parse_string(); $this->data = $this->_parse_string();
} }
if (!is_array($this->data)) { if (!is_array($this->data)) {
throw new \UnexpectedValueException('No data set yet.'); throw new \UnexpectedValueException('No data set yet.');
@@ -62,7 +60,7 @@ trait DatatypeTrait {
$result = []; $result = [];
foreach ($this->titles as $cName) { foreach ($this->titles as $cName) {
$column = array_column($this->data, $cName); $column = array_column($this->data, $cName);
$cDatatypes = array_map('ParseCsv\enums\DatatypeEnum::getValidTypeFromSample', $column); $cDatatypes = array_map(DatatypeEnum::class . '::getValidTypeFromSample', $column);
$result[$cName] = $this->getMostFrequentDatatypeForColumn($cDatatypes); $result[$cName] = $this->getMostFrequentDatatypeForColumn($cDatatypes);
} }
@@ -71,4 +69,35 @@ trait DatatypeTrait {
return !empty($this->data_types) ? $this->data_types : []; return !empty($this->data_types) ? $this->data_types : [];
} }
/**
* Check data type of titles / first row for auto detecting if this could be
* a heading line.
*
* Requires PHP >= 5.5
*
* @uses DatatypeEnum::getValidTypeFromSample
*
* @return bool
*/
public function autoDetectFileHasHeading() {
if (empty($this->data)) {
throw new \UnexpectedValueException('No data set yet.');
}
if ($this->heading) {
$firstRow = $this->titles;
} else {
$firstRow = $this->data[0];
}
$firstRow = array_filter($firstRow);
if (empty($firstRow)) {
return false;
}
$firstRowDatatype = array_map(DatatypeEnum::class . '::getValidTypeFromSample', $firstRow);
return $this->getMostFrequentDatatypeForColumn($firstRowDatatype) === DatatypeEnum::TYPE_STRING;
}
} }

View File

@@ -0,0 +1,22 @@
ID,dID,createdAt,else1,else2,else3,else4,else5,user,else6,else7,else8,else9,else10,else11,else12,else13,else14,else15,else16,else17,else18,else19,else20,else21,else22,else23,else24,else25,else26,else27,else28,else29,else30,else31,else32,else33,else34,else35,else36,else37,else38
418,0,2017-05-16,,,2018-01-22,22.01.2018 10:00:09,,admin,Ja,,10001,Abweichung,,10001,v1,1,ddd,100,1000,,HH,,v1,0,401,1,2,H1,,-1,10,1,111,Ja,2017-01-01,,12,0,11109,HH-100,default
419,0,2017-05-16,,,2017-05-16,14.07.2017 09:58:09,,admin,Ja,,,Abweichung,,10002,v2,1,ddd,200,500,DD,DD,,v2,1,402,2,4,H2,,-2,100,1,1111,Ja,2017-01-01,1,13,1,11109,DD-200,default
438,0,2017-05-16,,,2017-05-16,14.07.2017 09:58:29,,admin,Ja,,10021,Abweichung,,10021,v3,4,ddd,300,400,DD,DD,,v3,0,421,1,,H3,,-1,106,1,111,Ja,2017-05-08,,2,1,11109,DD-300,default
440,0,2017-05-16,,,,14.07.2017 09:58:53,,admin,Ja,,,Alt,,10023,v4,3,,400,500,BE,DD,,v4,1,423,3,,H4,,-3,143,1,1111,Ja,2017-01-01,1,33,1,11108,BE-400,default
441,0,2017-05-16,,,,14.07.2017 09:59:19,,admin,Ja,,,Fehlt,,10024,v5,3,,500,,BE,,,v5,2,424,4,,H5,,0,1435,0,111,Ja,2017-01-01,,12,1,0,BE-500,default
442,0,2017-05-16,,,,14.07.2017 10:00:46,,admin,Ja,,,Neu,,10025,v6,3,,100,,DD,,,v6,435,425,1,,H6,,0,10,0,1111,Ja,2017-01-01,,102,1,0,DD-100,default
443,0,2017-05-16,,,2017-07-04,14.07.2017 10:01:12,,admin,Ja,,,OK,,10026,v7,3,,200,200,DD,DD,,v7,32,426,2,2,H7,,0,100,0,111,Ja,2017-01-01,,77,1,0,DD-200,default
444,0,2017-05-16,,,,14.07.2017 10:02:13,,admin,Ja,,,Fehlt,,10027,v8,3,,300,,BE,,,v8,45,427,3,,H8,,0,200,0,1111,Ja,2017-01-01,,44,3,0,BE-300,default
445,0,2017-05-16,,,,14.07.2017 10:02:38,,admin,Ja,,,Fehlt,,10028,v9,3,,100,,BE,,,v9,45,428,4,,H9,,0,400,0,111,Ja,2017-01-01,,44,1,0,BE-100,default
446,0,2017-05-16,,,,14.07.2017 10:03:01,,admin,Ja,,,Fehlt,,10029,v10,3,,,400,,DD,,v10,45,429,,,H10,,0,1124,0,1111,Ja,2017-01-01,,89,1,0,,default
1 ID dID createdAt else1 else2 else3 else4 else5 user else6 else7 else8 else9 else10 else11 else12 else13 else14 else15 else16 else17 else18 else19 else20 else21 else22 else23 else24 else25 else26 else27 else28 else29 else30 else31 else32 else33 else34 else35 else36 else37 else38
2 418 0 2017-05-16 2018-01-22 22.01.2018 10:00:09 admin Ja 10001 Abweichung 10001 v1 1 ddd 100 1000 HH v1 0 401 1 2 H1 -1 10 1 111 Ja 2017-01-01 12 0 11109 HH-100 default
3 419 0 2017-05-16 2017-05-16 14.07.2017 09:58:09 admin Ja Abweichung 10002 v2 1 ddd 200 500 DD DD v2 1 402 2 4 H2 -2 100 1 1111 Ja 2017-01-01 1 13 1 11109 DD-200 default
4 438 0 2017-05-16 2017-05-16 14.07.2017 09:58:29 admin Ja 10021 Abweichung 10021 v3 4 ddd 300 400 DD DD v3 0 421 1 H3 -1 106 1 111 Ja 2017-05-08 2 1 11109 DD-300 default
5 440 0 2017-05-16 14.07.2017 09:58:53 admin Ja Alt 10023 v4 3 400 500 BE DD v4 1 423 3 H4 -3 143 1 1111 Ja 2017-01-01 1 33 1 11108 BE-400 default
6 441 0 2017-05-16 14.07.2017 09:59:19 admin Ja Fehlt 10024 v5 3 500 BE v5 2 424 4 H5 0 1435 0 111 Ja 2017-01-01 12 1 0 BE-500 default
7 442 0 2017-05-16 14.07.2017 10:00:46 admin Ja Neu 10025 v6 3 100 DD v6 435 425 1 H6 0 10 0 1111 Ja 2017-01-01 102 1 0 DD-100 default
8 443 0 2017-05-16 2017-07-04 14.07.2017 10:01:12 admin Ja OK 10026 v7 3 200 200 DD DD v7 32 426 2 2 H7 0 100 0 111 Ja 2017-01-01 77 1 0 DD-200 default
9 444 0 2017-05-16 14.07.2017 10:02:13 admin Ja Fehlt 10027 v8 3 300 BE v8 45 427 3 H8 0 200 0 1111 Ja 2017-01-01 44 3 0 BE-300 default
10 445 0 2017-05-16 14.07.2017 10:02:38 admin Ja Fehlt 10028 v9 3 100 BE v9 45 428 4 H9 0 400 0 111 Ja 2017-01-01 44 1 0 BE-100 default
11 446 0 2017-05-16 14.07.2017 10:03:01 admin Ja Fehlt 10029 v10 3 400 DD v10 45 429 H10 0 1124 0 1111 Ja 2017-01-01 89 1 0 default

View File

@@ -0,0 +1,6 @@
keyword
liability waiver
release of liability form
release of liability
sample waiver
sample waiver form
1 keyword
2 liability waiver
3 release of liability form
4 release of liability
5 sample waiver
6 sample waiver form

View File

@@ -58,8 +58,11 @@ class ConstructTest extends TestCase {
ob_start(); ob_start();
/** @noinspection PhpIncludeInspection */ /** @noinspection PhpIncludeInspection */
require $script_file; require $script_file;
if ($script_file != 'download.php') { $ob_get_clean = ob_get_clean();
$this->assertContains('<td>', ob_get_clean()); $verb = strtok($script_file, '_.');
if (!in_array($verb, ['download', 'save'], true)) {
$this->assertContains('<td>', $ob_get_clean);
} }
} }
chdir('..'); chdir('..');

View File

@@ -0,0 +1,82 @@
<?php
namespace ParseCsv\tests\methods;
use ParseCsv\Csv;
use PHPUnit\Framework\TestCase;
class DataRowCountTest extends TestCase {
/**
* CSV
* The CSV object
*
* @access protected
* @var Csv
*/
protected $csv;
/**
* Setup
* Setup our test environment objects
*
* @access public
*/
public function setUp() {
$this->csv = new Csv();
}
public function countRowsProvider() {
return [
'auto-double-enclosure' => [
'auto-double-enclosure.csv',
2,
],
'auto-single-enclosure' => [
'auto-single-enclosure.csv',
2,
],
'UTF-8_sep_row' => [
'datatype.csv',
3,
],
];
}
/**
* @dataProvider countRowsProvider
*
* @param string $file
* @param int $expectedRows
*/
public function testGetTotalRowCountFromFile($file, $expectedRows) {
$this->csv->heading = true;
$this->csv->load_data(__DIR__ . '/fixtures/' . $file);
$this->assertEquals($expectedRows, $this->csv->getTotalDataRowCount());
}
public function testGetTotalRowCountMissingEndingLineBreak() {
$this->csv->heading = false;
$this->csv->enclosure = '"';
$sInput = "86545235689,a\r\n34365587654,b\r\n13469874576,\"c\r\nd\"";
$this->csv->load_data($sInput);
$this->assertEquals(3, $this->csv->getTotalDataRowCount());
}
public function testGetTotalRowCountSingleEnclosure() {
$this->csv->heading = false;
$this->csv->enclosure = "'";
$sInput = "86545235689,a\r\n34365587654,b\r\n13469874576,\'c\r\nd\'";
$this->csv->load_data($sInput);
$this->assertEquals(3, $this->csv->getTotalDataRowCount());
}
public function testGetTotalRowCountSingleRow() {
$this->csv->heading = false;
$this->csv->enclosure = "'";
$sInput = "86545235689";
$this->csv->load_data($sInput);
$this->assertEquals(1, $this->csv->getTotalDataRowCount());
}
}

View File

@@ -8,12 +8,12 @@
namespace ParseCsv\tests\methods; namespace ParseCsv\tests\methods;
use PHPUnit\Framework\TestCase;
use ParseCsv\enums\DatatypeEnum; use ParseCsv\enums\DatatypeEnum;
use PHPUnit\Framework\TestCase;
class DatatypeTest extends TestCase class DatatypeTest extends TestCase {
{
public function testSampleIsValidInteger(){ public function testSampleIsValidInteger() {
$this->assertEquals(DatatypeEnum::TYPE_INT, DatatypeEnum::getValidTypeFromSample('1')); $this->assertEquals(DatatypeEnum::TYPE_INT, DatatypeEnum::getValidTypeFromSample('1'));
$this->assertEquals(DatatypeEnum::TYPE_INT, DatatypeEnum::getValidTypeFromSample('+1')); $this->assertEquals(DatatypeEnum::TYPE_INT, DatatypeEnum::getValidTypeFromSample('+1'));
$this->assertEquals(DatatypeEnum::TYPE_INT, DatatypeEnum::getValidTypeFromSample('-1')); $this->assertEquals(DatatypeEnum::TYPE_INT, DatatypeEnum::getValidTypeFromSample('-1'));
@@ -27,7 +27,7 @@ class DatatypeTest extends TestCase
$this->assertNotEquals(DatatypeEnum::TYPE_INT, DatatypeEnum::getValidTypeFromSample('2018-02-19')); $this->assertNotEquals(DatatypeEnum::TYPE_INT, DatatypeEnum::getValidTypeFromSample('2018-02-19'));
} }
public function testSampleIsValidBool(){ public function testSampleIsValidBool() {
$this->assertEquals(DatatypeEnum::TYPE_BOOL, DatatypeEnum::getValidTypeFromSample('true')); $this->assertEquals(DatatypeEnum::TYPE_BOOL, DatatypeEnum::getValidTypeFromSample('true'));
$this->assertEquals(DatatypeEnum::TYPE_BOOL, DatatypeEnum::getValidTypeFromSample('TRUE')); $this->assertEquals(DatatypeEnum::TYPE_BOOL, DatatypeEnum::getValidTypeFromSample('TRUE'));
$this->assertEquals(DatatypeEnum::TYPE_BOOL, DatatypeEnum::getValidTypeFromSample('false')); $this->assertEquals(DatatypeEnum::TYPE_BOOL, DatatypeEnum::getValidTypeFromSample('false'));
@@ -39,7 +39,7 @@ class DatatypeTest extends TestCase
$this->assertNotEquals(DatatypeEnum::TYPE_BOOL, DatatypeEnum::getValidTypeFromSample('0.1')); $this->assertNotEquals(DatatypeEnum::TYPE_BOOL, DatatypeEnum::getValidTypeFromSample('0.1'));
} }
public function testSampleIsValidFloat(){ public function testSampleIsValidFloat() {
$this->assertEquals(DatatypeEnum::TYPE_FLOAT, DatatypeEnum::getValidTypeFromSample('1.0')); $this->assertEquals(DatatypeEnum::TYPE_FLOAT, DatatypeEnum::getValidTypeFromSample('1.0'));
$this->assertEquals(DatatypeEnum::TYPE_FLOAT, DatatypeEnum::getValidTypeFromSample('-1.1')); $this->assertEquals(DatatypeEnum::TYPE_FLOAT, DatatypeEnum::getValidTypeFromSample('-1.1'));
$this->assertEquals(DatatypeEnum::TYPE_FLOAT, DatatypeEnum::getValidTypeFromSample('+1,1')); $this->assertEquals(DatatypeEnum::TYPE_FLOAT, DatatypeEnum::getValidTypeFromSample('+1,1'));
@@ -54,7 +54,7 @@ class DatatypeTest extends TestCase
$this->assertNotEquals(DatatypeEnum::TYPE_FLOAT, DatatypeEnum::getValidTypeFromSample('2018-02-19')); $this->assertNotEquals(DatatypeEnum::TYPE_FLOAT, DatatypeEnum::getValidTypeFromSample('2018-02-19'));
} }
public function testSampleIsValidDate(){ public function testSampleIsValidDate() {
$this->assertEquals(DatatypeEnum::TYPE_DATE, DatatypeEnum::getValidTypeFromSample('2018-02-19')); $this->assertEquals(DatatypeEnum::TYPE_DATE, DatatypeEnum::getValidTypeFromSample('2018-02-19'));
$this->assertEquals(DatatypeEnum::TYPE_DATE, DatatypeEnum::getValidTypeFromSample('18-2-19')); $this->assertEquals(DatatypeEnum::TYPE_DATE, DatatypeEnum::getValidTypeFromSample('18-2-19'));
$this->assertEquals(DatatypeEnum::TYPE_DATE, DatatypeEnum::getValidTypeFromSample('01.02.2018')); $this->assertEquals(DatatypeEnum::TYPE_DATE, DatatypeEnum::getValidTypeFromSample('01.02.2018'));

View File

@@ -0,0 +1,101 @@
<?php
namespace ParseCsv\tests\methods;
/**
* This is a very simple implementation of a stream wrapper. All URLs are mapped
* to just one buffer. It exists to prove that ParseCsv can read and write
* streams.
*
* @see https://www.php.net/manual/en/class.streamwrapper.php
*/
class ExampleStream {
private static $position = 0;
private static $stream_content;
public function stream_open($uri, $mode) {
if (strpos($mode, 'a') === false) {
self::$position = 0;
}
if (strpos($mode, 'w') !== false) {
self::$stream_content = '';
}
return true;
}
public function stream_read($count) {
$ret = substr(self::$stream_content, self::$position, $count);
self::$position += strlen($ret);
return $ret;
}
public function stream_write($data) {
$left = substr(self::$stream_content, 0, self::$position);
$right = substr(self::$stream_content, self::$position + strlen($data));
self::$stream_content = $left . $data . $right;
self::$position += strlen($data);
return strlen($data);
}
public function stream_stat() {
return ['size' => strlen(self::$stream_content)];
}
public function stream_tell() {
return self::$position;
}
public function stream_eof() {
return self::$position >= strlen(self::$stream_content);
}
public function url_stat() {
return ['size' => strlen(self::$stream_content)];
}
public function stream_seek($offset, $whence) {
switch ($whence) {
case SEEK_SET:
if ($offset < strlen(self::$stream_content) && $offset >= 0) {
self::$position = $offset;
return true;
} else {
return false;
}
break;
case SEEK_CUR:
if ($offset >= 0) {
self::$position += $offset;
return true;
} else {
return false;
}
break;
case SEEK_END:
if (strlen(self::$stream_content) + $offset >= 0) {
self::$position = strlen(self::$stream_content) + $offset;
return true;
} else {
return false;
}
break;
default:
return false;
}
}
public function stream_lock($operation) {
return true;
}
public function stream_metadata() {
return false;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace ParseCsv\tests\methods;
/**
* Class HasToString is just a helper to test if cells can be objects.
*/
class ObjectThatHasToStringMethod {
public function __toString() {
return 'some value';
}
}

View File

@@ -4,6 +4,11 @@ namespace ParseCsv\tests\methods;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/**
* This test checks for backwards compatibility: Does it work to
* - require the old "parsecsv.lib.php" instead of composer autoloading?
* - use the old class name "parseCSV"?
*/
class OldRequireTest extends TestCase { class OldRequireTest extends TestCase {
protected function setUp() { protected function setUp() {
@@ -15,7 +20,7 @@ class OldRequireTest extends TestCase {
} }
/** /**
* @runInSeparateProcess because download.php uses header() * @runInSeparateProcess so that disabled autoloading has an effect
*/ */
public function testOldLibWithoutComposer() { public function testOldLibWithoutComposer() {
@@ -25,4 +30,16 @@ class OldRequireTest extends TestCase {
$this->assertEquals($output, []); $this->assertEquals($output, []);
$this->assertEquals(0, $return_var); $this->assertEquals(0, $return_var);
} }
/**
* @runInSeparateProcess so that disabled autoloading has an effect
*/
public function testOldLibWithOldClassName() {
file_put_contents('__eval.php', '<?php require "parsecsv.lib.php"; new parseCSV;');
exec("php __eval.php", $output, $return_var);
unlink('__eval.php');
$this->assertEquals($output, []);
$this->assertEquals(0, $return_var);
}
} }

View File

@@ -0,0 +1,21 @@
<?php
namespace ParseCsv\tests\methods;
use ParseCsv\Csv;
use PHPUnit\Framework\TestCase;
class OutputTest extends TestCase {
/**
* @runInSeparateProcess because download.php uses header()
*/
public function testOutputWithFourParameters() {
$csv = new Csv();
$data = [0 => ['a', 'b', 'c'], 1 => ['d', 'e', 'f']];
$fields = ['col1', 'col2', 'col3'];
$output = $csv->output('test.csv', $data, $fields, ',');
$expected = "col1,col2,col3\ra,b,c\rd,e,f\r";
self::assertEquals($expected, $output);
}
}

View File

@@ -22,13 +22,13 @@ class ParseTest extends TestCase {
$this->csv = new Csv(); $this->csv = new Csv();
} }
public function test_parse() { public function testParse() {
// can we trick 'is_readable' into whining? See #67. // can we trick 'is_readable' into whining? See #67.
$this->parse_repetitive_string('c:/looks/like/a/path'); $this->parseRepetitiveString('c:/looks/like/a/path');
$this->parse_repetitive_string('http://looks/like/an/url'); $this->parseRepetitiveString('http://looks/like/an/url');
} }
private function parse_repetitive_string($content) { private function parseRepetitiveString($content) {
$this->csv->delimiter = ';'; $this->csv->delimiter = ';';
$this->csv->heading = false; $this->csv->heading = false;
$success = $this->csv->parse(str_repeat($content . ';', 500)); $success = $this->csv->parse(str_repeat($content . ';', 500));
@@ -41,9 +41,11 @@ class ParseTest extends TestCase {
} }
/** /**
* @depends test_parse * @depends testParse
* *
* @dataProvider autoDetectionProvider * @dataProvider autoDetectionProvider
*
* @param string $file
*/ */
public function testSepRowAutoDetection($file) { public function testSepRowAutoDetection($file) {
// This file (parse_test.php) is encoded in UTF-8, hence comparison will // This file (parse_test.php) is encoded in UTF-8, hence comparison will
@@ -85,7 +87,8 @@ class ParseTest extends TestCase {
$this->csv->enclosure = '"'; $this->csv->enclosure = '"';
$sInput = "86545235689,a\r\n34365587654,b\r\n13469874576,\"c\r\nd\""; $sInput = "86545235689,a\r\n34365587654,b\r\n13469874576,\"c\r\nd\"";
$expected_data = [86545235689, 34365587654, 13469874576]; $expected_data = [86545235689, 34365587654, 13469874576];
$actual_data = $this->csv->parse_string($sInput);
$actual_data = $this->invokeMethod($this->csv, '_parse_string', array($sInput));
$actual_column = array_map('reset', $actual_data); $actual_column = array_map('reset', $actual_data);
$this->assertEquals($expected_data, $actual_column); $this->assertEquals($expected_data, $actual_column);
$this->assertEquals([ $this->assertEquals([
@@ -95,7 +98,7 @@ class ParseTest extends TestCase {
], array_map('next', $actual_data)); ], array_map('next', $actual_data));
} }
public function test_single_column() { public function testSingleColumn() {
$this->csv->auto(__DIR__ . '/../example_files/single_column.csv'); $this->csv->auto(__DIR__ . '/../example_files/single_column.csv');
$expected = [ $expected = [
['SMS' => '0444'], ['SMS' => '0444'],
@@ -106,11 +109,8 @@ class ParseTest extends TestCase {
$this->assertEquals($expected, $this->csv->data); $this->assertEquals($expected, $this->csv->data);
} }
public function test_Piwik_data() { public function testMatomoData() {
if (!function_exists('array_column')) { // Matomo (Piwik) export cannot be read with
// function only available in PHP >= 5.5
return;
}
$this->csv->use_mb_convert_encoding = true; $this->csv->use_mb_convert_encoding = true;
$this->csv->output_encoding = 'UTF-8'; $this->csv->output_encoding = 'UTF-8';
$this->csv->auto(__DIR__ . '/../example_files/Piwik_API_download.csv'); $this->csv->auto(__DIR__ . '/../example_files/Piwik_API_download.csv');
@@ -129,16 +129,61 @@ class ParseTest extends TestCase {
], $aCity); ], $aCity);
} }
/**
* Tests if we can handle BOMs in string data, in contrast to loading files.
*/
public function testStringWithLeadingBOM() {
$string_with_bom = strtr(
file_get_contents(__DIR__ . '/../example_files/UTF-8_with_BOM_and_sep_row.csv'),
["sep=;\n" => '']);
// Is the BOM still there?
self::assertSame(0xEF, ord($string_with_bom));
$this->csv->output_encoding = 'UTF-8';
$this->csv->delimiter = ';';
self::assertTrue($this->csv->load_data($string_with_bom));
self::assertTrue($this->csv->parse($this->csv->file_data));
// This also tests if ::load_data removed the BOM from the data;
// otherwise the 'title' column would have 3 extra bytes.
$this->assertEquals([
'title',
'isbn',
'publishedAt',
], array_keys(reset($this->csv->data)));
$titles = array_column($this->csv->data, 'title');
$this->assertEquals([
'Красивая кулинария',
'The Wine Connoisseurs',
'Weißwein',
], $titles);
}
public function testWithMultipleNewlines() {
$this->csv->auto(__DIR__ . '/../example_files/multiple_empty_lines.csv');
$aElse9 = array_column($this->csv->data, 'else9');
/** @noinspection SpellCheckingInspection */
$this->assertEquals([
'Abweichung',
'Abweichung',
'Abweichung',
'Alt',
'Fehlt',
'Neu',
'OK',
'Fehlt',
'Fehlt',
'Fehlt',
], $aElse9);
}
/** /**
* @depends testSepRowAutoDetection * @depends testSepRowAutoDetection
*/ */
public function testGetColumnDatatypes() { public function testGetColumnDatatypes() {
if (!function_exists('array_column')) {
// getDatatypes requires array_column, but that
// function is only available in PHP >= 5.5
return;
}
$this->csv->auto(__DIR__ . '/fixtures/datatype.csv'); $this->csv->auto(__DIR__ . '/fixtures/datatype.csv');
$this->csv->getDatatypes(); $this->csv->getDatatypes();
$expected = [ $expected = [
@@ -153,29 +198,31 @@ class ParseTest extends TestCase {
$this->assertEquals($expected, $this->csv->data_types); $this->assertEquals($expected, $this->csv->data_types);
} }
/**
public function testDataArrayKeysWhenSettingOffsetWithHeading() { * @depends testSepRowAutoDetection
$this->csv->offset = 2; */
public function testAutoDetectFileHasHeading() {
$this->csv->auto(__DIR__ . '/fixtures/datatype.csv'); $this->csv->auto(__DIR__ . '/fixtures/datatype.csv');
$expected = [ $this->assertTrue($this->csv->autoDetectFileHasHeading());
'title',
'isbn',
'publishedAt',
'published',
'count',
'price'
];
$this->assertEquals($expected, array_keys($this->csv->data[0])); $this->csv->heading = false;
$this->csv->auto(__DIR__ . '/fixtures/datatype.csv');
$this->assertTrue($this->csv->autoDetectFileHasHeading());
$this->csv->heading = false;
$sInput = "86545235689\r\n34365587654\r\n13469874576";
$this->csv->auto($sInput);
$this->assertFalse($this->csv->autoDetectFileHasHeading());
$this->csv->heading = true;
$sInput = "86545235689\r\n34365587654\r\n13469874576";
$this->csv->auto($sInput);
$this->assertFalse($this->csv->autoDetectFileHasHeading());
} }
public function testDataArrayKeysWhenSettingOffsetWithoutHeading() { public function testVeryLongNonExistingFile() {
$this->csv->heading = false; $this->csv->parse(str_repeat('long_string', PHP_MAXPATHLEN));
$this->csv->offset = 2; $this->csv->auto(str_repeat('long_string', PHP_MAXPATHLEN));
$this->csv->auto(__DIR__ . '/fixtures/datatype.csv');
$expected = range(0, 5, 1);
$this->assertEquals($expected, array_keys($this->csv->data[0]));
} }
protected function _get_magazines_data() { protected function _get_magazines_data() {
@@ -219,4 +266,34 @@ class ParseTest extends TestCase {
$this->assertArrayHasKey('column1', $csv->data[0], 'Data parsed incorrectly with enclosure ' . $enclosure); $this->assertArrayHasKey('column1', $csv->data[0], 'Data parsed incorrectly with enclosure ' . $enclosure);
$this->assertEquals('value1', $csv->data[0]['column1'], 'Data parsed incorrectly with enclosure ' . $enclosure); $this->assertEquals('value1', $csv->data[0]['column1'], 'Data parsed incorrectly with enclosure ' . $enclosure);
} }
/**
* Call protected/private method of a class.
*
* @param object &$object Instantiated object that we will run method on.
* @param string $methodName Method name to call
* @param array $parameters Array of parameters to pass into method.
*
* @return mixed Method return.
*/
private function invokeMethod(&$object, $methodName, array $parameters = array()) {
$reflection = new \ReflectionClass(get_class($object));
$method = $reflection->getMethod($methodName);
$method->setAccessible(true);
return $method->invokeArgs($object, $parameters);
}
public function testWaiverFieldSeparator() {
$this->assertSame(false, $this->csv->auto(__DIR__ . '/../example_files/waiver_field_separator.csv'));
$expected = [
'liability waiver',
'release of liability form',
'release of liability',
'sample waiver',
'sample waiver form',
];
$actual = array_column($this->csv->data, 'keyword');
$this->assertSame($expected, $actual);
}
} }

View File

@@ -1,11 +1,11 @@
<?php <?php
namespace ParseCsv\tests\methods; namespace ParseCsv\tests\methods;
use ParseCsv\Csv; use ParseCsv\Csv;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class SaveTest extends TestCase class SaveTest extends TestCase {
{
/** @var Csv */ /** @var Csv */
private $csv; private $csv;
@@ -49,6 +49,13 @@ class SaveTest extends TestCase
$this->saveAndCompare($expected); $this->saveAndCompare($expected);
} }
public function testSaveWithNewHeader() {
$this->csv->linefeed = "\n";
$this->csv->titles = array("NewTitle");
$expected = "NewTitle\n0444\n5555\n";
$this->saveAndCompare($expected);
}
public function testSaveWithoutHeader() { public function testSaveWithoutHeader() {
$this->csv->linefeed = "\n"; $this->csv->linefeed = "\n";
$this->csv->heading = false; $this->csv->heading = false;

View File

@@ -0,0 +1,71 @@
<?php
namespace ParseCsv\tests\methods;
use ParseCsv\Csv;
use PHPUnit\Framework\TestCase;
/**
* Writes roughly 1 MB of data. This is useful because of a limit of 8 KB
* encountered with stream operations in certain PHP versions.
*/
class StreamTest extends TestCase {
public function setUp() {
static $done;
if ($done) {
// Make sure we register the stream just once - or PHP will scream.
return;
}
stream_wrapper_register("example", ExampleStream::class)
or die("Failed to register protocol");
$done = 1;
}
public function testReadStream() {
$csv = new Csv();
// Write data to our stream:
$filename = 'example://tmp';
copy(__DIR__ . '/fixtures/datatype.csv', $filename);
$many_dots = str_repeat('.', 1000 * 1000) . ";;;;;\n";
file_put_contents($filename, $many_dots, FILE_APPEND);
self::assertSame(';', $csv->auto(file_get_contents($filename)));
self::assertCount(4, $csv->data);
self::assertCount(6, reset($csv->data));
}
public function testWriteStream() {
$csv = new Csv();
$csv->linefeed = "\n";
$many_dots = str_repeat('.', 1000 * 1000);
$csv->data = [
[
'Name' => 'Rudolf',
'Question' => 'Which color is his nose?',
],
[
'Name' => 'Sponge Bob',
'Question' => 'Which shape are his pants?',
],
[
'Name' => $many_dots,
'Question' => 'Can you count one million dots?',
],
];
// Just export the first column, but with a new name
$csv->titles = ['Name' => 'Character'];
// Write data to our stream:
$filename = 'example://data';
copy(__DIR__ . '/fixtures/datatype.csv', $filename);
self::assertSame(true, $csv->save($filename));
$expected = "Character\nRudolf\nSponge Bob\n";
$expected .= $many_dots . "\n";
self::assertSame($expected, file_get_contents($filename));
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace ParseCsv\tests\methods;
use ParseCsv\Csv;
use PHPUnit\Framework\TestCase;
class UnparseTest extends Testcase {
/** @var Csv */
private $csv;
/**
* Setup our test environment objects; will be called before each test.
*/
public function setUp() {
$this->csv = new Csv();
$this->csv->auto(__DIR__ . '/fixtures/auto-double-enclosure.csv');
}
public function testUnparseWithParameters() {
$fields = array('a' => 'AA', 'b' => 'BB');
$data = [['a' => 'value1', 'b' => 'value2']];
$csv_object = new Csv();
$csv_string = $csv_object->unparse($data, $fields);
$this->assertEquals("AA,BB\rvalue1,value2\r", $csv_string);
$csv_object = new Csv();
$csv_object->linefeed = "\n";
$csv_string = $csv_object->unparse([[55, 66]]);
$this->assertEquals("55,66\n", $csv_string);
$csv_object = new Csv();
$data2 = [['a' => "multi\rline", 'b' => 'value2']];
$csv_object->enclosure = "'";
$csv_string = $csv_object->unparse($data2, $fields);
$this->assertEquals("AA,BB\r'multi\rline',value2\r", $csv_string);
}
public function testUnparseDefault() {
$expected = "column1,column2\rvalue1,value2\rvalue3,value4\r";
$this->unparseAndCompare($expected);
}
public function testUnparseDefaultWithoutHeading() {
$this->csv->heading = false;
$this->csv->auto(__DIR__ . '/fixtures/auto-double-enclosure.csv');
$expected = "column1,column2\rvalue1,value2\rvalue3,value4\r";
$this->unparseAndCompare($expected);
}
public function testUnparseRenameFields() {
$expected = "C1,C2\rvalue1,value2\rvalue3,value4\r";
$this->unparseAndCompare($expected, array("C1", "C2"));
}
public function testReorderFields() {
$expected = "column2,column1\rvalue2,value1\rvalue4,value3\r";
$this->unparseAndCompare($expected, array("column2", "column1"));
}
public function testSubsetFields() {
$expected = "column1\rvalue1\rvalue3\r";
$this->unparseAndCompare($expected, array("column1"));
}
public function testReorderAndRenameFields() {
$fields = array(
'column2' => 'C2',
'column1' => 'C1',
);
$expected = "C2,C1\rvalue2,value1\rvalue4,value3\r";
$this->unparseAndCompare($expected, $fields);
}
public function testUnparseDefaultFirstRowMissing() {
unset($this->csv->data[0]);
$expected = "column1,column2\rvalue3,value4\r";
$this->unparseAndCompare($expected);
}
public function testUnparseDefaultWithoutData() {
unset($this->csv->data[0]);
unset($this->csv->data[1]);
$expected = "column1,column2\r";
$this->unparseAndCompare($expected);
}
public function testObjectCells() {
$this->csv->data = [
[
'column1' => new ObjectThatHasToStringMethod(),
'column2' => 'boring',
],
];
$this->csv->linefeed = "\n";
$expected = "column1,column2\nsome value,boring\n";
$this->unparseAndCompare($expected);
}
private function unparseAndCompare($expected, $fields = array()) {
$str = $this->csv->unparse($this->csv->data, $fields);
$this->assertEquals($expected, $str);
}
}

View File

@@ -11,7 +11,6 @@ class BaseClass extends TestCase {
* CSV * CSV
* The parseCSV object * The parseCSV object
* *
* @access protected
* @var Csv * @var Csv
*/ */
protected $csv; protected $csv;
@@ -19,10 +18,8 @@ class BaseClass extends TestCase {
/** /**
* Setup * Setup
* Setup our test environment objects * Setup our test environment objects
*
* @access public
*/ */
public function setUp() { protected function setUp() {
$this->csv = new Csv(); $this->csv = new Csv();
} }

View File

@@ -21,21 +21,93 @@ class ConditionsTest extends BaseClass {
]); ]);
} }
public function testRating() { public function testRatingEquals() {
$this->csv->conditions = 'rating < 3'; $rating_of_3 = [
$this->_compareWithExpected([ 'The Last Templar',
'The Broker (Paperback)',
'Without Blood (Paperback)',
];
$this->csv->conditions = 'rating = 3';
$this->_compareWithExpected($rating_of_3);
$this->csv->conditions = 'rating is 3';
$this->_compareWithExpected($rating_of_3);
$this->csv->conditions = 'rating equals 3';
$this->_compareWithExpected($rating_of_3);
}
public function testRatingNotEquals() {
$rating_not_4 = [
'The Killing Kind', 'The Killing Kind',
'The Third Secret', 'The Third Secret',
]); 'The Last Templar',
'The Traveller',
'Prey',
'The Broker (Paperback)',
'Without Blood (Paperback)',
'State of Fear (Paperback)',
'Digital Fortress : A Thriller (Mass Market Paperback)',
'Angels & Demons (Mass Market Paperback)',
];
// $this->csv->conditions = 'rating != 4';
// $this->_compareWithExpected($rating_not_4);
$this->csv->conditions = 'rating is not 4';
$this->_compareWithExpected($rating_not_4);
// $this->csv->conditions = 'rating does not contain 4';
// $this->_compareWithExpected($rating_not_4);
}
$this->csv->conditions = 'rating >= 5'; public function testRatingLessThan() {
$this->_compareWithExpected([ $less_than_1 = [
'The Killing Kind',
'The Third Secret',
];
$this->csv->conditions = 'rating < 1';
$this->_compareWithExpected($less_than_1);
$this->csv->conditions = 'rating is less than 1';
$this->_compareWithExpected($less_than_1);
}
public function testRatingLessOrEquals() {
$less_or_equals_1 = [
'The Killing Kind',
'The Third Secret',
];
$this->csv->conditions = 'rating <= 1';
$this->_compareWithExpected($less_or_equals_1);
$this->csv->conditions = 'rating is less than or equals 1';
$this->_compareWithExpected($less_or_equals_1);
}
public function testRatingGreaterThan() {
$greater_4 = [
'The Traveller', 'The Traveller',
'Prey', 'Prey',
'State of Fear (Paperback)', 'State of Fear (Paperback)',
'Digital Fortress : A Thriller (Mass Market Paperback)', 'Digital Fortress : A Thriller (Mass Market Paperback)',
'Angels & Demons (Mass Market Paperback)', 'Angels & Demons (Mass Market Paperback)',
]); ];
$this->csv->conditions = 'rating > 4';
$this->_compareWithExpected($greater_4);
$this->csv->conditions = 'rating is greater than 4';
$this->_compareWithExpected($greater_4);
}
public function testRatingGreaterOrEquals() {
$greater_or_equal_4 = [
'The Traveller',
'Crisis Four',
'Prey',
'State of Fear (Paperback)',
'The Rule of Four (Paperback)',
'Deception Point (Paperback)',
'Digital Fortress : A Thriller (Mass Market Paperback)',
'Angels & Demons (Mass Market Paperback)',
'The Da Vinci Code (Hardcover)',
];
$this->csv->conditions = 'rating >= 4';
$this->_compareWithExpected($greater_or_equal_4);
$this->csv->conditions = 'rating is greater than or equals 4';
$this->_compareWithExpected($greater_or_equal_4);
} }
public function testTitleContainsSecretOrCode() { public function testTitleContainsSecretOrCode() {

View File

@@ -57,7 +57,7 @@ class DefaultValuesPropertiesTest extends TestCase {
} }
public function test_sort_type_default() { public function test_sort_type_default() {
$this->assertNull($this->csv->sort_type); $this->assertEquals('regular', $this->csv->sort_type);
} }
public function test_delimiter_default() { public function test_delimiter_default() {

View File

@@ -0,0 +1,79 @@
<?php
namespace ParseCsv\tests\properties;
/**
* Tests related to the $offset property
*/
class OffsetTest extends BaseClass {
public function testOffsetOfOne() {
$this->csv->offset = 1;
$this->csv->auto(__DIR__ . '/../methods/fixtures/datatype.csv');
$this->assertCount(3, $this->csv->data);
if (!function_exists('array_column')) {
// function only available in PHP >= 5.5
return;
}
$expected = [
'Красивая кулинария',
'The Wine Connoisseurs',
'Weißwein',
];
$actual = array_column($this->csv->data, 'title');
$this->assertEquals($expected, $actual);
}
public function numberRangeZeroToFourProvider() {
return array_map(function ($number) {
return [$number];
}, range(0, 4));
}
/**
* @dataProvider numberRangeZeroToFourProvider
*
* @param integer $offset
*/
public function testOffsetOfOneNoHeader($offset) {
$this->csv->offset = $offset;
$this->csv->heading = false;
$this->csv->auto(__DIR__ . '/../methods/fixtures/datatype.csv');
$this->assertCount(4 - $offset, $this->csv->data);
}
public function testDataArrayKeysWhenSettingOffsetWithHeading() {
$this->csv->offset = 2;
$this->csv->auto(__DIR__ . '/../methods/fixtures/datatype.csv');
$expected = [
[
'title' => 'The Wine Connoisseurs',
'isbn' => '2547-8548-2541',
'publishedAt' => '12.12.2011',
'published' => 'TRUE',
'count' => '',
'price' => 20.33,
],
[
'title' => 'Weißwein',
'isbn' => '1313-4545-8875',
'publishedAt' => '23.02.2012',
'published' => 'false',
'count' => 10,
'price' => 10,
],
];
$this->assertEquals($expected, $this->csv->data);
}
public function testDataArrayKeysWhenSettingOffsetWithoutHeading() {
$this->csv->heading = false;
$this->csv->offset = 2;
$this->csv->auto(__DIR__ . '/../methods/fixtures/datatype.csv');
$expected = range(0, 5, 1);
$this->assertEquals($expected, array_keys($this->csv->data[0]));
}
}

View File

@@ -3,6 +3,7 @@
namespace ParseCsv\tests\properties; namespace ParseCsv\tests\properties;
use ParseCsv\Csv; use ParseCsv\Csv;
use ParseCsv\enums\SortEnum;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class PublicPropertiesTest extends TestCase { class PublicPropertiesTest extends TestCase {
@@ -145,4 +146,26 @@ class PublicPropertiesTest extends TestCase {
$this->assertCount($counter, $this->properties); $this->assertCount($counter, $this->properties);
} }
public function testDefaultSortTypeIsRegular() {
$this->assertEquals(SortEnum::SORT_TYPE_REGULAR, $this->csv->sort_type);
}
public function testSetSortType() {
$this->csv->sort_type = 'numeric';
$this->assertEquals(SortEnum::SORT_TYPE_NUMERIC, $this->csv->sort_type);
$this->csv->sort_type = 'string';
$this->assertEquals(SortEnum::SORT_TYPE_STRING, $this->csv->sort_type);
}
public function testGetSorting() {
$this->csv->sort_type = 'numeric';
$sorting = SortEnum::getSorting($this->csv->sort_type);
$this->assertEquals(SORT_NUMERIC, $sorting);
$this->csv->sort_type = 'string';
$sorting = SortEnum::getSorting($this->csv->sort_type);
$this->assertEquals(SORT_STRING, $sorting);
}
} }