Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/aimogen-pro/res/parsecsv/src/Csv.php
Keine Baseline-Datei – Diff nur gegen leer.
1
-
1
+
<?php
2
+
3
+
namespace ParseCsv;
4
+
5
+
use Illuminate\Support\Collection;
6
+
use ParseCsv\enums\FileProcessingModeEnum;
7
+
use ParseCsv\enums\SortEnum;
8
+
use ParseCsv\extensions\DatatypeTrait;
9
+
10
+
class Csv {
11
+
12
+
/*
13
+
https://github.com/parsecsv/parsecsv-for-php
14
+
15
+
Fully conforms to the specifications lined out on Wikipedia:
16
+
- http://en.wikipedia.org/wiki/Comma-separated_values
17
+
18
+
Based on the concept of Ming Hong Ng's CsvFileParser class:
19
+
- http://minghong.blogspot.com/2006/07/csv-parser-for-php.html
20
+
21
+
(The MIT license)
22
+
23
+
Copyright (c) 2014 Jim Myhrberg.
24
+
25
+
Permission is hereby granted, free of charge, to any person obtaining a copy
26
+
of this software and associated documentation files (the "Software"), to deal
27
+
in the Software without restriction, including without limitation the rights
28
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
29
+
copies of the Software, and to permit persons to whom the Software is
30
+
furnished to do so, subject to the following conditions:
31
+
32
+
The above copyright notice and this permission notice shall be included in
33
+
all copies or substantial portions of the Software.
34
+
35
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
38
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
39
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
40
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
41
+
THE SOFTWARE.
42
+
43
+
For code examples, please read the files within the 'examples' dir.
44
+
*/
45
+
46
+
/**
47
+
* Configuration
48
+
* - set these options with $object->var_name = 'value';
49
+
*/
50
+
51
+
/**
52
+
* Header row:
53
+
* Use first line/entry as field names
54
+
*
55
+
* @var bool
56
+
*/
57
+
public $heading = true;
58
+
59
+
/**
60
+
* Override field names
61
+
*
62
+
* @var array
63
+
*/
64
+
public $fields = array();
65
+
66
+
/**
67
+
* Sort CSV by this field
68
+
*
69
+
* @var string|null
70
+
*/
71
+
public $sort_by = null;
72
+
73
+
/**
74
+
* Reverse the sort direction
75
+
*
76
+
* @var bool
77
+
*/
78
+
public $sort_reverse = false;
79
+
80
+
/**
81
+
* Sort behavior passed to sort methods
82
+
*
83
+
* regular = SORT_REGULAR
84
+
* numeric = SORT_NUMERIC
85
+
* string = SORT_STRING
86
+
*
87
+
* @var string|null
88
+
*/
89
+
public $sort_type = SortEnum::SORT_TYPE_REGULAR;
90
+
91
+
/**
92
+
* Field delimiter character
93
+
*
94
+
* @var string
95
+
*/
96
+
public $delimiter = ',';
97
+
98
+
/**
99
+
* Enclosure character
100
+
*
101
+
* This is useful for cell values that are either multi-line
102
+
* or contain the field delimiter character.
103
+
*
104
+
* @var string
105
+
*/
106
+
public $enclosure = '"';
107
+
108
+
/**
109
+
* Force enclosing all columns.
110
+
*
111
+
* If false, only cells that are either multi-line or
112
+
* contain the field delimiter character are enclosed
113
+
* in the $enclosure char.
114
+
*
115
+
* @var bool
116
+
*/
117
+
public $enclose_all = false;
118
+
119
+
/**
120
+
* Basic SQL-Like conditions for row matching
121
+
*
122
+
* @var string|null
123
+
*/
124
+
public $conditions = null;
125
+
126
+
/**
127
+
* Number of rows to ignore from beginning of data. If present, the heading
128
+
* row is also counted (if $this->heading == true). In other words,
129
+
* $offset == 1 and $offset == 0 have the same meaning in that situation.
130
+
*
131
+
* @var int|null
132
+
*/
133
+
public $offset = null;
134
+
135
+
/**
136
+
* Limits the number of returned rows to the specified amount
137
+
*
138
+
* @var int|null
139
+
*/
140
+
public $limit = null;
141
+
142
+
/**
143
+
* Number of rows to analyze when attempting to auto-detect delimiter
144
+
*
145
+
* @var int
146
+
*/
147
+
public $auto_depth = 15;
148
+
149
+
/**
150
+
* Characters that should be ignored when attempting to auto-detect delimiter
151
+
*
152
+
* @var string
153
+
*/
154
+
public $auto_non_chars = "a-zA-Z0-9\n\r";
155
+
156
+
/**
157
+
* preferred delimiter characters, only used when all filtering method
158
+
* returns multiple possible delimiters (happens very rarely)
159
+
*
160
+
* @var string
161
+
*/
162
+
public $auto_preferred = ",;\t.:|";
163
+
164
+
/**
165
+
* banned delimiter characters (author Szabi)
166
+
*
167
+
* @var array
168
+
*/
169
+
public $auto_banned = array('_');
170
+
171
+
/**
172
+
* Should we convert the CSV character encoding?
173
+
* Used for both parse and unparse operations.
174
+
*
175
+
* @var bool
176
+
*/
177
+
public $convert_encoding = false;
178
+
179
+
/**
180
+
* Set the input encoding
181
+
*
182
+
* @var string
183
+
*/
184
+
public $input_encoding = 'ISO-8859-1';
185
+
186
+
/**
187
+
* Set the output encoding
188
+
*
189
+
* @var string
190
+
*/
191
+
public $output_encoding = 'ISO-8859-1';
192
+
193
+
/**
194
+
* Whether to use mb_convert_encoding() instead of iconv().
195
+
*
196
+
* The former is platform-independent whereas the latter is the traditional
197
+
* default go-to solution.
198
+
*
199
+
* @var bool (if false, iconv() is used)
200
+
*/
201
+
public $use_mb_convert_encoding = false;
202
+
203
+
/**
204
+
* Line feed characters used by unparse, save, and output methods
205
+
* Popular choices are "\r\n" and "\n".
206
+
*
207
+
* @var string
208
+
*/
209
+
public $linefeed = "\r";
210
+
211
+
/**
212
+
* Sets the output delimiter used by the output method
213
+
*
214
+
* @var string
215
+
*/
216
+
public $output_delimiter = ',';
217
+
218
+
/**
219
+
* Sets the output filename
220
+
*
221
+
* @var string
222
+
*/
223
+
public $output_filename = 'data.csv';
224
+
225
+
/**
226
+
* keep raw file data in memory after successful parsing (useful for debugging)
227
+
*
228
+
* @var bool
229
+
*/
230
+
public $keep_file_data = false;
231
+
232
+
/**
233
+
* Internal variables
234
+
*/
235
+
236
+
/**
237
+
* File
238
+
* Current Filename
239
+
*
240
+
* @var string
241
+
*/
242
+
public $file;
243
+
244
+
/**
245
+
* File Data
246
+
* Current file data
247
+
*
248
+
* @var string
249
+
*/
250
+
public $file_data;
251
+
252
+
/**
253
+
* Error
254
+
* Contains the error code if one occurred
255
+
*
256
+
* 0 = No errors found. Everything should be fine :)
257
+
* 1 = Hopefully correctable syntax error was found.
258
+
* 2 = Enclosure character (double quote by default)
259
+
* was found in non-enclosed field. This means
260
+
* the file is either corrupt, or does not
261
+
* standard CSV formatting. Please validate
262
+
* the parsed data yourself.
263
+
*
264
+
* @var int
265
+
*/
266
+
public $error = 0;
267
+
268
+
/**
269
+
* Detailed error information
270
+
*
271
+
* @var array
272
+
*/
273
+
public $error_info = array();
274
+
275
+
/**
276
+
* $titles has 4 distinct tasks:
277
+
* 1. After reading in CSV data, $titles will contain the column headers
278
+
* present in the data.
279
+
*
280
+
* 2. It defines which fields from the $data array to write e.g. when
281
+
* calling unparse(), and in which order. This lets you skip columns you
282
+
* don't want in your output, but are present in $data.
283
+
* See examples/save_to_file_without_header_row.php.
284
+
*
285
+
* 3. It lets you rename columns. See StreamTest::testWriteStream for an
286
+
* example.
287
+
*
288
+
* 4. When writing data and $header is true, then $titles is also used for
289
+
* the first row.
290
+
*
291
+
* @var array
292
+
*/
293
+
public $titles = array();
294
+
295
+
/**
296
+
* Two-dimensional array of CSV data.
297
+
* The first dimension are the line numbers. Each line is represented as an array with field names as keys.
298
+
*
299
+
* @var array<array>
300
+
*/
301
+
public $data = array();
302
+
303
+
use DatatypeTrait;
304
+
305
+
/**
306
+
* Class constructor
307
+
*
308
+
* @param string|null $data The CSV string or a direct file path.
309
+
*
310
+
* WARNING: Supplying file paths here is
311
+
* deprecated. Use parseFile() instead.
312
+
*
313
+
* @param int|null $offset Number of rows to ignore from the
314
+
* beginning of the data
315
+
* @param int|null $limit Limits the number of returned rows
316
+
* to specified amount
317
+
* @param string|null $conditions Basic SQL-like conditions for row
318
+
* matching
319
+
* @param null|true $keep_file_data Keep raw file data in memory after
320
+
* successful parsing
321
+
* (useful for debugging)
322
+
*/
323
+
public function __construct($data = null, $offset = null, $limit = null, $conditions = null, $keep_file_data = null) {
324
+
$this->init($offset, $limit, $conditions, $keep_file_data);
325
+
326
+
if (!empty($data)) {
327
+
$this->parse($data);
328
+
}
329
+
}
330
+
331
+
/**
332
+
* @param int|null $offset Number of rows to ignore from the
333
+
* beginning of the data
334
+
* @param int|null $limit Limits the number of returned rows
335
+
* to specified amount
336
+
* @param string|null $conditions Basic SQL-like conditions for row
337
+
* matching
338
+
* @param null|true $keep_file_data Keep raw file data in memory after
339
+
* successful parsing
340
+
* (useful for debugging)
341
+
*/
342
+
public function init($offset = null, $limit = null, $conditions = null, $keep_file_data = null) {
343
+
if (!is_null($offset)) {
344
+
$this->offset = $offset;
345
+
}
346
+
347
+
if (!is_null($limit)) {
348
+
$this->limit = $limit;
349
+
}
350
+
351
+
if (!is_null($conditions)) {
352
+
$this->conditions = $conditions;
353
+
}
354
+
355
+
if (!is_null($keep_file_data)) {
356
+
$this->keep_file_data = $keep_file_data;
357
+
}
358
+
}
359
+
360
+
// ==============================================
361
+
// ----- [ Main Functions ] ---------------------
362
+
// ==============================================
363
+
364
+
/**
365
+
* Parse a CSV file or string
366
+
*
367
+
* @param string|null $dataString The CSV string or a direct file path
368
+
* WARNING: Supplying file paths here is
369
+
* deprecated and will trigger an
370
+
* E_USER_DEPRECATED error.
371
+
* @param int|null $offset Number of rows to ignore from the
372
+
* beginning of the data
373
+
* @param int|null $limit Limits the number of returned rows to
374
+
* specified amount
375
+
* @param string|null $conditions Basic SQL-like conditions for row
376
+
* matching
377
+
*
378
+
* @return bool True on success
379
+
*/
380
+
public function parse($dataString = null, $offset = null, $limit = null, $conditions = null) {
381
+
if (is_null($dataString)) {
382
+
$this->data = $this->parseFile();
383
+
return $this->data !== false;
384
+
}
385
+
386
+
if (empty($dataString)) {
387
+
return false;
388
+
}
389
+
390
+
$this->init($offset, $limit, $conditions);
391
+
392
+
if (strlen($dataString) <= PHP_MAXPATHLEN && is_readable($dataString)) {
393
+
$this->file = $dataString;
394
+
$this->data = $this->parseFile();
395
+
trigger_error(
396
+
'Supplying file paths to parse() will no longer ' .
397
+
'be supported in a future version of ParseCsv. ' .
398
+
'Use ->parseFile() instead.',
399
+
E_USER_DEPRECATED
400
+
);
401
+
} else {
402
+
$this->file = null;
403
+
$this->file_data = &$dataString;
404
+
$this->data = $this->_parse_string();
405
+
}
406
+
407
+
return $this->data !== false;
408
+
}
409
+
410
+
/**
411
+
* Save changes, or write a new file and/or data.
412
+
*
413
+
* @param string $file File location to save to
414
+
* @param array $data 2D array of data
415
+
* @param bool $append Append current data to end of target CSV, if file
416
+
* exists
417
+
* @param array $fields Field names. Sets the header. If it is not set
418
+
* $this->titles would be used instead.
419
+
*
420
+
* @return bool
421
+
* True on success
422
+
*/
423
+
public function save($file = '', $data = array(), $append = FileProcessingModeEnum::MODE_FILE_OVERWRITE, $fields = array()) {
424
+
if (empty($file)) {
425
+
$file = &$this->file;
426
+
}
427
+
428
+
$mode = FileProcessingModeEnum::getAppendMode($append);
429
+
$is_php = (bool) preg_match('/\.php$/i', $file);
430
+
431
+
return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode);
432
+
}
433
+
434
+
/**
435
+
* Generate a CSV-based string for output.
436
+
*
437
+
* Useful for exports in web applications.
438
+
*
439
+
* @param string|null $filename If a filename is specified here or in the
440
+
* object, headers and data will be output
441
+
* directly to browser as a downloadable
442
+
* file. This file doesn't have to exist on
443
+
* the server; the parameter only affects
444
+
* how the download is called to the
445
+
* browser.
446
+
* @param array[] $data 2D array with data
447
+
* @param array $fields Field names
448
+
* @param string|null $delimiter character used to separate data
449
+
*
450
+
* @return string The resulting CSV string
451
+
*/
452
+
public function output($filename = null, $data = array(), $fields = array(), $delimiter = null) {
453
+
if (empty($filename)) {
454
+
$filename = $this->output_filename;
455
+
}
456
+
457
+
if ($delimiter === null) {
458
+
$delimiter = $this->output_delimiter;
459
+
}
460
+
461
+
$flat_string = $this->unparse($data, $fields, null, null, $delimiter);
462
+
463
+
if (!is_null($filename)) {
464
+
$mime = $delimiter === "\t" ?
465
+
'text/tab-separated-values' :
466
+
'application/csv';
467
+
header('Content-type: ' . $mime);
468
+
header('Content-Length: ' . strlen($flat_string));
469
+
header('Cache-Control: no-cache, must-revalidate');
470
+
header('Pragma: no-cache');
471
+
header('Expires: 0');
472
+
header('Content-Disposition: attachment; filename="' . $filename . '"; modification-date="' . date('r') . '";');
473
+
474
+
echo $flat_string;
475
+
}
476
+
477
+
return $flat_string;
478
+
}
479
+
480
+
/**
481
+
* Convert character encoding
482
+
*
483
+
* Specify the encoding to use for the next parsing or unparsing.
484
+
* Calling this function will not change the data held in the object immediately.
485
+
*
486
+
* @param string|null $input Input character encoding
487
+
* If the value null is passed, the existing input encoding remains set (default: ISO-8859-1).
488
+
* @param string|null $output Output character encoding, uses default if left blank
489
+
* If the value null is passed, the existing input encoding remains set (default: ISO-8859-1).
490
+
*
491
+
* @return void
492
+
*/
493
+
public function encoding($input = null, $output = null) {
494
+
$this->convert_encoding = true;
495
+
if (!is_null($input)) {
496
+
$this->input_encoding = $input;
497
+
}
498
+
499
+
if (!is_null($output)) {
500
+
$this->output_encoding = $output;
501
+
}
502
+
}
503
+
504
+
/**
505
+
* Auto-detect delimiter: Find delimiter by analyzing a specific number of
506
+
* rows to determine most probable delimiter character
507
+
*
508
+
* @param string|null $file Local CSV file
509
+
* Supplying CSV data (file content) here is deprecated.
510
+
* For CSV data, please use autoDetectionForDataString().
511
+
* Support for CSV data will be removed in v2.0.0.
512
+
* @param bool $parse True/false parse file directly
513
+
* @param int|null $search_depth Number of rows to analyze
514
+
* @param string|null $preferred Preferred delimiter characters
515
+
* @param string|null $enclosure Enclosure character, default is double quote (").
516
+
*
517
+
* @return string|false The detected field delimiter
518
+
*/
519
+
public function auto($file = null, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) {
520
+
if (is_null($file)) {
521
+
$file = $this->file;
522
+
}
523
+
524
+
if (empty($search_depth)) {
525
+
$search_depth = $this->auto_depth;
526
+
}
527
+
528
+
if (is_null($enclosure)) {
529
+
$enclosure = $this->enclosure;
530
+
} else {
531
+
$this->enclosure = $enclosure;
532
+
}
533
+
534
+
if (is_null($preferred)) {
535
+
$preferred = $this->auto_preferred;
536
+
}
537
+
538
+
if (empty($this->file_data)) {
539
+
if ($this->_check_data($file)) {
540
+
$data = &$this->file_data;
541
+
} else {
542
+
return false;
543
+
}
544
+
} else {
545
+
$data = &$this->file_data;
546
+
}
547
+
548
+
$this->autoDetectionForDataString($data, $parse, $search_depth, $preferred, $enclosure);
549
+
550
+
return $this->delimiter;
551
+
}
552
+
553
+
/**
554
+
* Auto-detect delimiter: Find delimiter by analyzing a specific number of
555
+
* rows to determine most probable delimiter character (Author: Szabi)
556
+
*
557
+
* @param string|null $data Load CSV data
558
+
* @param bool $parse True/false parse file directly
559
+
* @param int|null $search_depth Number of rows to analyze
560
+
* @param string|null $preferred Preferred delimiter characters
561
+
* @param string|null $enclosure Enclosure character, default is double quote (").
562
+
*
563
+
* @return string|false The detected field delimiter
564
+
*/
565
+
public function autoDirectData($data = null, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) {
566
+
if (empty($search_depth)) {
567
+
$search_depth = $this->auto_depth;
568
+
}
569
+
570
+
if (is_null($enclosure)) {
571
+
$enclosure = $this->enclosure;
572
+
} else {
573
+
$this->enclosure = $enclosure;
574
+
}
575
+
576
+
if (is_null($preferred)) {
577
+
$preferred = $this->auto_preferred;
578
+
}
579
+
$this->loadDataString(rtrim($data, "\r\n"));
580
+
581
+
$this->autoDetectionForDataString($data, $parse, $search_depth, $preferred, $enclosure);
582
+
583
+
return $this->delimiter;
584
+
}
585
+
586
+
587
+
public function autoDetectionForDataString($data, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) {
588
+
$this->file_data = &$data;
589
+
if (!$this->_detect_and_remove_sep_row_from_data($data)) {
590
+
$this->_guess_delimiter($search_depth, $preferred, $enclosure, $data);
591
+
}
592
+
593
+
// parse data
594
+
if ($parse) {
595
+
$this->data = $this->_parse_string();
596
+
}
597
+
598
+
return $this->delimiter;
599
+
}
600
+
601
+
/**
602
+
* Get total number of data rows (exclusive heading line if present) in CSV
603
+
* without parsing the whole data string.
604
+
*
605
+
* @return bool|int
606
+
*/
607
+
public function getTotalDataRowCount() {
608
+
if (empty($this->file_data)) {
609
+
return false;
610
+
}
611
+
612
+
$data = $this->file_data;
613
+
614
+
$this->_detect_and_remove_sep_row_from_data($data);
615
+
616
+
$pattern = sprintf('/%1$s[^%1$s]*%1$s/i', $this->enclosure);
617
+
preg_match_all($pattern, $data, $matches);
618
+
619
+
/** @var array[] $matches */
620
+
foreach ($matches[0] as $match) {
621
+
if (empty($match) || (strpos($match, $this->enclosure) === false)) {
622
+
continue;
623
+
}
624
+
625
+
$replace = str_replace(["\r", "\n"], '', $match);
626
+
$data = str_replace($match, $replace, $data);
627
+
}
628
+
629
+
$headingRow = $this->heading ? 1 : 0;
630
+
631
+
return substr_count($data, "\r")
632
+
+ substr_count($data, "\n")
633
+
- substr_count($data, "\r\n")
634
+
- $headingRow;
635
+
}
636
+
637
+
// ==============================================
638
+
// ----- [ Core Functions ] ---------------------
639
+
// ==============================================
640
+
/**
641
+
* Read file to string and call _parse_string()
642
+
*
643
+
* @param string|null $file Path to a CSV file.
644
+
* If configured in files such as php.ini,
645
+
* the path may also contain a protocol:
646
+
* https://example.org/some/file.csv
647
+
*
648
+
* @return array<array>|false
649
+
*/
650
+
public function parseFile($file = null) {
651
+
if (is_null($file)) {
652
+
$file = $this->file;
653
+
}
654
+
655
+
/**
656
+
* @see self::keep_file_data
657
+
* Usually, _parse_string will clean this
658
+
* Instead of leaving stale data for the next parseFile call behind.
659
+
*/
660
+
if (empty($this->file_data) && !$this->loadFile($file)) {
661
+
return false;
662
+
}
663
+
664
+
if (empty($this->file_data)) {
665
+
return false;
666
+
}
667
+
return $this->data = $this->_parse_string();
668
+
}
669
+
670
+
/**
671
+
* Parse data from string (author Szabi)
672
+
*
673
+
* @param string|null $data Data string
674
+
*
675
+
* @return array<array>|false
676
+
*/
677
+
public function parseData($data = null) {
678
+
679
+
$this->loadDataString(rtrim($data, "\r\n"));
680
+
681
+
return $this->data = $this->_parse_string();
682
+
}
683
+
684
+
/**
685
+
* Internal function to parse CSV strings to arrays.
686
+
*
687
+
* If you need BOM detection or character encoding conversion, please call
688
+
* $csv->load_data($your_data_string) first, followed by a call to
689
+
* $csv->parse($csv->file_data).
690
+
*
691
+
* To detect field separators, please use auto() instead.
692
+
*
693
+
* @param string|null $data CSV data
694
+
*
695
+
* @return array<array>|false
696
+
* 2D array with CSV data, or false on failure
697
+
*/
698
+
protected function _parse_string($data = null) {
699
+
if (empty($data)) {
700
+
if ($this->_check_data()) {
701
+
$data = &$this->file_data;
702
+
} else {
703
+
return false;
704
+
}
705
+
}
706
+
707
+
$white_spaces = str_replace($this->delimiter, '', " \t\x0B\0");
708
+
709
+
$rows = array();
710
+
$row = array();
711
+
$row_count = 0;
712
+
$current = '';
713
+
$head = !empty($this->fields) ? $this->fields : array();
714
+
$col = 0;
715
+
$enclosed = false;
716
+
$was_enclosed = false;
717
+
$strlen = strlen($data);
718
+
719
+
// force the parser to process end of data as a character (false) when
720
+
// data does not end with a line feed or carriage return character.
721
+
$lch = $data[$strlen - 1];
722
+
if ($lch != "\n" && $lch != "\r") {
723
+
$data .= "\n";
724
+
$strlen++;
725
+
}
726
+
727
+
// walk through each character
728
+
for ($i = 0; $i < $strlen; $i++) {
729
+
$ch = isset($data[$i]) ? $data[$i] : false;
730
+
$nch = isset($data[$i + 1]) ? $data[$i + 1] : false;
731
+
732
+
// open/close quotes, and inline quotes
733
+
if ($ch == $this->enclosure) {
734
+
if (!$enclosed) {
735
+
if (ltrim($current, $white_spaces) == '') {
736
+
$enclosed = true;
737
+
$was_enclosed = true;
738
+
} else {
739
+
$this->error = 2;
740
+
$error_row = count($rows) + 1;
741
+
$error_col = $col + 1;
742
+
$index = $error_row . '-' . $error_col;
743
+
if (!isset($this->error_info[$index])) {
744
+
$this->error_info[$index] = array(
745
+
'type' => 2,
746
+
'info' => 'Syntax error found on row ' . $error_row . '. Non-enclosed fields can not contain double-quotes.',
747
+
'row' => $error_row,
748
+
'field' => $error_col,
749
+
'field_name' => !empty($head[$col]) ? $head[$col] : null,
750
+
);
751
+
}
752
+
753
+
$current .= $ch;
754
+
}
755
+
} elseif ($nch == $this->enclosure) {
756
+
$current .= $ch;
757
+
$i++;
758
+
} elseif ($nch != $this->delimiter && $nch != "\r" && $nch != "\n") {
759
+
$x = $i + 1;
760
+
while (isset($data[$x]) && ltrim($data[$x], $white_spaces) == '') {
761
+
$x++;
762
+
}
763
+
if ($data[$x] == $this->delimiter) {
764
+
$enclosed = false;
765
+
$i = $x;
766
+
} else {
767
+
if ($this->error < 1) {
768
+
$this->error = 1;
769
+
}
770
+
771
+
$error_row = count($rows) + 1;
772
+
$error_col = $col + 1;
773
+
$index = $error_row . '-' . $error_col;
774
+
if (!isset($this->error_info[$index])) {
775
+
$this->error_info[$index] = array(
776
+
'type' => 1,
777
+
'info' =>
778
+
'Syntax error found on row ' . (count($rows) + 1) . '. ' .
779
+
'A single double-quote was found within an enclosed string. ' .
780
+
'Enclosed double-quotes must be escaped with a second double-quote.',
781
+
'row' => count($rows) + 1,
782
+
'field' => $col + 1,
783
+
'field_name' => !empty($head[$col]) ? $head[$col] : null,
784
+
);
785
+
}
786
+
787
+
$current .= $ch;
788
+
$enclosed = false;
789
+
}
790
+
} else {
791
+
$enclosed = false;
792
+
}
793
+
// end of field/row/csv
794
+
} elseif ((in_array($ch, [$this->delimiter, "\n", "\r", false], true)) && !$enclosed) {
795
+
$key = !empty($head[$col]) ? $head[$col] : $col;
796
+
$row[$key] = $was_enclosed ? $current : trim($current);
797
+
$current = '';
798
+
$was_enclosed = false;
799
+
$col++;
800
+
801
+
// end of row
802
+
if (in_array($ch, ["\n", "\r", false], true)) {
803
+
if ($this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions)) {
804
+
if ($this->heading && empty($head)) {
805
+
$head = $row;
806
+
} elseif (empty($this->fields) || (!empty($this->fields) && (($this->heading && $row_count > 0) || !$this->heading))) {
807
+
if (!empty($this->sort_by) && !empty($row[$this->sort_by])) {
808
+
$sort_field = $row[$this->sort_by];
809
+
if (isset($rows[$sort_field])) {
810
+
$rows[$sort_field . '_0'] = &$rows[$sort_field];
811
+
unset($rows[$sort_field]);
812
+
$sn = 1;
813
+
while (isset($rows[$sort_field . '_' . $sn])) {
814
+
$sn++;
815
+
}
816
+
$rows[$sort_field . '_' . $sn] = $row;
817
+
} else {
818
+
$rows[$sort_field] = $row;
819
+
}
820
+
821
+
} else {
822
+
$rows[] = $row;
823
+
}
824
+
}
825
+
}
826
+
827
+
$row = array();
828
+
$col = 0;
829
+
$row_count++;
830
+
831
+
if ($this->sort_by === null && $this->limit !== null && count($rows) == $this->limit) {
832
+
$i = $strlen;
833
+
}
834
+
835
+
if ($ch == "\r" && $nch == "\n") {
836
+
$i++;
837
+
}
838
+
}
839
+
840
+
// append character to current field
841
+
} else {
842
+
$current .= $ch;
843
+
}
844
+
}
845
+
846
+
$this->titles = $head;
847
+
if (!empty($this->sort_by)) {
848
+
$sort_type = SortEnum::getSorting($this->sort_type);
849
+
$this->sort_reverse ? krsort($rows, $sort_type) : ksort($rows, $sort_type);
850
+
851
+
if ($this->offset !== null || $this->limit !== null) {
852
+
$rows = array_slice($rows, ($this->offset === null ? 0 : $this->offset), $this->limit, true);
853
+
}
854
+
}
855
+
856
+
if (!$this->keep_file_data) {
857
+
$this->file_data = null;
858
+
}
859
+
860
+
return $rows;
861
+
}
862
+
863
+
/**
864
+
* Create CSV data string from array
865
+
*
866
+
* @param array[] $data 2D array with data
867
+
* @param array $fields field names
868
+
* @param bool $append if true, field names will not be output
869
+
* @param bool $is_php if a php die() call should be put on the
870
+
* first line of the file, this is later
871
+
* ignored when read.
872
+
* @param string|null $delimiter field delimiter to use
873
+
*
874
+
* @return string CSV data
875
+
*/
876
+
public function unparse($data = array(), $fields = array(), $append = FileProcessingModeEnum::MODE_FILE_OVERWRITE, $is_php = false, $delimiter = null) {
877
+
if (!is_array($data) || empty($data)) {
878
+
$data = &$this->data;
879
+
} else {
880
+
/** @noinspection ReferenceMismatchInspection */
881
+
$this->data = $data;
882
+
}
883
+
884
+
if (!is_array($fields) || empty($fields)) {
885
+
$fields = &$this->titles;
886
+
}
887
+
888
+
if ($delimiter === null) {
889
+
$delimiter = $this->delimiter;
890
+
}
891
+
892
+
$string = $is_php ? "<?php header('Status: 403'); die(' '); ?>" . $this->linefeed : '';
893
+
$entry = array();
894
+
895
+
// create heading
896
+
/** @noinspection ReferenceMismatchInspection */
897
+
$fieldOrder = $this->_validate_fields_for_unparse($fields);
898
+
if (!$fieldOrder && !empty($data)) {
899
+
$column_count = count($data[0]);
900
+
$columns = range(0, $column_count - 1, 1);
901
+
$fieldOrder = array_combine($columns, $columns);
902
+
}
903
+
904
+
if ($this->heading && !$append && !empty($fields)) {
905
+
foreach ($fieldOrder as $column_name) {
906
+
$entry[] = $this->_enclose_value($column_name, $delimiter);
907
+
}
908
+
909
+
$string .= implode($delimiter, $entry) . $this->linefeed;
910
+
$entry = array();
911
+
}
912
+
// create data
913
+
foreach ($data as $row) {
914
+
foreach (array_keys($fieldOrder) as $index) {
915
+
$cell_value = $row[$index];
916
+
$entry[] = $this->_enclose_value($cell_value, $delimiter);
917
+
}
918
+
919
+
$string .= implode($delimiter, $entry) . $this->linefeed;
920
+
$entry = array();
921
+
}
922
+
923
+
if ($this->convert_encoding) {
924
+
/** @noinspection PhpComposerExtensionStubsInspection
925
+
*
926
+
* If you receive an error at the following 3 lines, you must enable
927
+
* the following PHP extension:
928
+
*
929
+
* - if $use_mb_convert_encoding is true: mbstring
930
+
* - if $use_mb_convert_encoding is false: iconv
931
+
*/
932
+
$string = $this->use_mb_convert_encoding ?
933
+
mb_convert_encoding($string, $this->output_encoding, $this->input_encoding) :
934
+
iconv($this->input_encoding, $this->output_encoding, $string);
935
+
}
936
+
937
+
return $string;
938
+
}
939
+
940
+
/**
941
+
* @param array $fields
942
+
*
943
+
* @return array|false
944
+
*/
945
+
private function _validate_fields_for_unparse(array $fields) {
946
+
if (empty($fields)) {
947
+
$fields = $this->titles;
948
+
}
949
+
950
+
if (empty($fields)) {
951
+
return array();
952
+
}
953
+
954
+
// this is needed because sometime titles property is overwritten instead of using fields parameter!
955
+
$titlesOnParse = !empty($this->data) ? array_keys(reset($this->data)) : array();
956
+
957
+
// both are identical, also in ordering OR we have no data (only titles)
958
+
if (empty($titlesOnParse) || array_values($fields) === array_values($titlesOnParse)) {
959
+
return array_combine($fields, $fields);
960
+
}
961
+
962
+
// if renaming given by: $oldName => $newName (maybe with reorder and / or subset):
963
+
// todo: this will only work if titles are unique
964
+
$fieldOrder = array_intersect(array_flip($fields), $titlesOnParse);
965
+
if (!empty($fieldOrder)) {
966
+
return array_flip($fieldOrder);
967
+
}
968
+
969
+
$fieldOrder = array_intersect($fields, $titlesOnParse);
970
+
if (!empty($fieldOrder)) {
971
+
return array_combine($fieldOrder, $fieldOrder);
972
+
}
973
+
974
+
// original titles are not given in fields. that is okay if count is okay.
975
+
if (count($fields) != count($titlesOnParse)) {
976
+
throw new \UnexpectedValueException(
977
+
"The specified fields do not match any titles and do not match column count.\n" .
978
+
"\$fields was " . print_r($fields, true) .
979
+
"\$titlesOnParse was " . print_r($titlesOnParse, true));
980
+
}
981
+
982
+
return array_combine($titlesOnParse, $fields);
983
+
}
984
+
985
+
/**
986
+
* Load local file or string.
987
+
*
988
+
* Only use this function if auto() and parse() don't handle your data well.
989
+
*
990
+
* This function load_data() is able to handle BOMs and encodings. The data
991
+
* is stored within the $this->file_data class field.
992
+
*
993
+
* @param string|null $input CSV file path or CSV data as a string
994
+
*
995
+
* Supplying CSV data (file content) here is deprecated.
996
+
* For CSV data, please use loadDataString().
997
+
* Support for CSV data will be removed in v2.0.0.
998
+
*
999
+
* @return bool True on success
1000
+
* @deprecated Use loadDataString() or loadFile() instead.
1001
+
*/
1002
+
public function load_data($input = null) {
1003
+
return $this->loadFile($input);
1004
+
}
1005
+
1006
+
/**
1007
+
* Load a file, but don't parse it.
1008
+
*
1009
+
* Only use this function if auto() and parseFile() don't handle your data well.
1010
+
*
1011
+
* This function is able to handle BOMs and encodings. The data
1012
+
* is stored within the $this->file_data class field.
1013
+
*
1014
+
* @param string|null $file CSV file path
1015
+
*
1016
+
* @return bool True on success
1017
+
*/
1018
+
public function loadFile($file = null) {
1019
+
$data = null;
1020
+
1021
+
if (is_null($file)) {
1022
+
$data = $this->_rfile($this->file);
1023
+
} elseif (\strlen($file) <= PHP_MAXPATHLEN && file_exists($file)) {
1024
+
$data = $this->_rfile($file);
1025
+
if ($this->file != $file) {
1026
+
$this->file = $file;
1027
+
}
1028
+
} else {
1029
+
// It is CSV data as a string.
1030
+
1031
+
// WARNING:
1032
+
// Supplying CSV data to load_data() will no longer
1033
+
// be supported in a future version of ParseCsv.
1034
+
// This function will return false for invalid paths from v2.0.0 onwards.
1035
+
1036
+
// Use ->loadDataString() instead.
1037
+
1038
+
$data = $file;
1039
+
}
1040
+
1041
+
return $this->loadDataString($data);
1042
+
}
1043
+
1044
+
/**
1045
+
* Load a data string, but don't parse it.
1046
+
*
1047
+
* Only use this function if autoDetectionForDataString() and parse() don't handle your data well.
1048
+
*
1049
+
* This function is able to handle BOMs and encodings. The data
1050
+
* is stored within the $this->file_data class field.
1051
+
*
1052
+
* @param string|null $file_path CSV file path
1053
+
*
1054
+
* @return bool True on success
1055
+
*/
1056
+
public function loadDataString($data) {
1057
+
if (!empty($data)) {
1058
+
if (strpos($data, "\xef\xbb\xbf") === 0) {
1059
+
// strip off BOM (UTF-8)
1060
+
$data = substr($data, 3);
1061
+
$this->encoding('UTF-8');
1062
+
} elseif (strpos($data, "\xff\xfe") === 0) {
1063
+
// strip off BOM (UTF-16 little endian)
1064
+
$data = substr($data, 2);
1065
+
$this->encoding("UCS-2LE");
1066
+
} elseif (strpos($data, "\xfe\xff") === 0) {
1067
+
// strip off BOM (UTF-16 big endian)
1068
+
$data = substr($data, 2);
1069
+
$this->encoding("UTF-16");
1070
+
}
1071
+
1072
+
if ($this->convert_encoding && $this->input_encoding !== $this->output_encoding) {
1073
+
/** @noinspection PhpComposerExtensionStubsInspection
1074
+
*
1075
+
* If you receive an error at the following 3 lines, you must enable
1076
+
* the following PHP extension:
1077
+
*
1078
+
* - if $use_mb_convert_encoding is true: mbstring
1079
+
* - if $use_mb_convert_encoding is false: iconv
1080
+
*/
1081
+
$data = $this->use_mb_convert_encoding ?
1082
+
mb_convert_encoding($data, $this->output_encoding, $this->input_encoding) :
1083
+
iconv($this->input_encoding, $this->output_encoding, $data);
1084
+
}
1085
+
1086
+
if (substr($data, -1) != "\n") {
1087
+
$data .= "\n";
1088
+
}
1089
+
1090
+
$this->file_data = &$data;
1091
+
return true;
1092
+
}
1093
+
1094
+
return false;
1095
+
}
1096
+
1097
+
// ==============================================
1098
+
// ----- [ Internal Functions ] -----------------
1099
+
// ==============================================
1100
+
1101
+
/**
1102
+
* Validate a row against specified conditions
1103
+
*
1104
+
* @param array $row array with values from a row
1105
+
* @param string|null $conditions specified conditions that the row must match
1106
+
*
1107
+
* @return bool
1108
+
*/
1109
+
protected function _validate_row_conditions($row = array(), $conditions = null) {
1110
+
if (!empty($row)) {
1111
+
if (!empty($conditions)) {
1112
+
$condition_array = (strpos($conditions, ' OR ') !== false) ?
1113
+
explode(' OR ', $conditions) :
1114
+
array($conditions);
1115
+
$or = '';
1116
+
foreach ($condition_array as $key => $value) {
1117
+
if (strpos($value, ' AND ') !== false) {
1118
+
$value = explode(' AND ', $value);
1119
+
$and = '';
1120
+
1121
+
foreach ($value as $k => $v) {
1122
+
$and .= $this->_validate_row_condition($row, $v);
1123
+
}
1124
+
1125
+
$or .= (strpos($and, '0') !== false) ? '0' : '1';
1126
+
} else {
1127
+
$or .= $this->_validate_row_condition($row, $value);
1128
+
}
1129
+
}
1130
+
1131
+
return strpos($or, '1') !== false;
1132
+
}
1133
+
1134
+
return true;
1135
+
}
1136
+
1137
+
return false;
1138
+
}
1139
+
1140
+
/**
1141
+
* Validate a row against a single condition
1142
+
*
1143
+
* @param array $row array with values from a row
1144
+
* @param string $condition specified condition that the row must match
1145
+
*
1146
+
* @return string single 0 or 1
1147
+
*/
1148
+
protected function _validate_row_condition($row, $condition) {
1149
+
$operators = array(
1150
+
'=',
1151
+
'equals',
1152
+
'is',
1153
+
'!=',
1154
+
'is not',
1155
+
'<',
1156
+
'is less than',
1157
+
'>',
1158
+
'is greater than',
1159
+
'<=',
1160
+
'is less than or equals',
1161
+
'>=',
1162
+
'is greater than or equals',
1163
+
'contains',
1164
+
'does not contain',
1165
+
'is number',
1166
+
'is not number',
1167
+
);
1168
+
1169
+
$operators_regex = array();
1170
+
1171
+
foreach ($operators as $value) {
1172
+
$operators_regex[] = preg_quote($value, '/');
1173
+
}
1174
+
1175
+
$operators_regex = implode('|', $operators_regex);
1176
+
1177
+
if (preg_match('/^(.+) (' . $operators_regex . ') (.+)$/i', trim($condition), $capture)) {
1178
+
$field = $capture[1];
1179
+
$op = strtolower($capture[2]);
1180
+
$value = $capture[3];
1181
+
if ($op == 'equals' && preg_match('/^(.+) is (less|greater) than or$/i', $field, $m)) {
1182
+
$field = $m[1];
1183
+
$op = strtolower($m[2]) == 'less' ? '<=' : '>=';
1184
+
}
1185
+
if ($op == 'is' && preg_match('/^(less|greater) than (.+)$/i', $value, $m)) {
1186
+
$value = $m[2];
1187
+
$op = strtolower($m[1]) == 'less' ? '<' : '>';
1188
+
}
1189
+
if ($op == 'is' && preg_match('/^not (.+)$/i', $value, $m)) {
1190
+
$value = $m[1];
1191
+
$op = '!=';
1192
+
}
1193
+
1194
+
if (preg_match('/^([\'"])(.*)([\'"])$/', $value, $capture) && $capture[1] == $capture[3]) {
1195
+
$value = strtr($capture[2], array(
1196
+
"\\n" => "\n",
1197
+
"\\r" => "\r",
1198
+
"\\t" => "\t",
1199
+
));
1200
+
1201
+
$value = stripslashes($value);
1202
+
}
1203
+
1204
+
if (array_key_exists($field, $row)) {
1205
+
$op_equals = in_array($op, ['=', 'equals', 'is'], true);
1206
+
if ($op_equals && $row[$field] == $value) {
1207
+
return '1';
1208
+
} elseif ($op_equals && $value == 'number' && is_numeric($row[$field])) {
1209
+
return '1';
1210
+
} elseif (($op == '!=' || $op == 'is not') && $value == 'number' && !is_numeric($row[$field])) {
1211
+
return '1';
1212
+
} elseif (($op == '!=' || $op == 'is not') && $row[$field] != $value) {
1213
+
return '1';
1214
+
} elseif (($op == '<' || $op == 'is less than') && $row[$field] < $value) {
1215
+
return '1';
1216
+
} elseif (($op == '>' || $op == 'is greater than') && $row[$field] > $value) {
1217
+
return '1';
1218
+
} elseif (($op == '<=' || $op == 'is less than or equals') && $row[$field] <= $value) {
1219
+
return '1';
1220
+
} elseif (($op == '>=' || $op == 'is greater than or equals') && $row[$field] >= $value) {
1221
+
return '1';
1222
+
} elseif ($op == 'contains' && preg_match('/' . preg_quote($value, '/') . '/i', $row[$field])) {
1223
+
return '1';
1224
+
} elseif ($op == 'does not contain' && !preg_match('/' . preg_quote($value, '/') . '/i', $row[$field])) {
1225
+
return '1';
1226
+
} else {
1227
+
return '0';
1228
+
}
1229
+
}
1230
+
}
1231
+
1232
+
return '1';
1233
+
}
1234
+
1235
+
/**
1236
+
* Validates if the row is within the offset or not if sorting is disabled
1237
+
*
1238
+
* @param int $current_row the current row number being processed
1239
+
*
1240
+
* @return bool
1241
+
*/
1242
+
protected function _validate_offset($current_row) {
1243
+
return
1244
+
$this->sort_by !== null ||
1245
+
$this->offset === null ||
1246
+
$current_row >= $this->offset ||
1247
+
($this->heading && $current_row == 0);
1248
+
}
1249
+
1250
+
/**
1251
+
* Enclose values if needed
1252
+
* - only used by unparse()
1253
+
*
1254
+
* @param string|null $value Cell value to process
1255
+
* @param string $delimiter Character to put between cells on the same row
1256
+
*
1257
+
* @return string Processed value
1258
+
*/
1259
+
protected function _enclose_value($value, $delimiter) {
1260
+
if ($value !== null && $value != '') {
1261
+
$delimiter_quoted = $delimiter ?
1262
+
preg_quote($delimiter, '/') . "|"
1263
+
: '';
1264
+
$enclosure_quoted = preg_quote($this->enclosure, '/');
1265
+
$pattern = "/" . $delimiter_quoted . $enclosure_quoted . "|\n|\r/i";
1266
+
if ($this->enclose_all || preg_match($pattern, $value) || strpos($value, ' ') === 0 || substr($value, -1) == ' ') {
1267
+
$value = str_replace($this->enclosure, $this->enclosure . $this->enclosure, $value);
1268
+
$value = $this->enclosure . $value . $this->enclosure;
1269
+
}
1270
+
}
1271
+
1272
+
return $value;
1273
+
}
1274
+
1275
+
/**
1276
+
* Check file data
1277
+
*
1278
+
* @param string|null $file local filename
1279
+
*
1280
+
* @return bool
1281
+
*/
1282
+
protected function _check_data($file = null) {
1283
+
if (empty($this->file_data)) {
1284
+
if (is_null($file)) {
1285
+
$file = $this->file;
1286
+
}
1287
+
1288
+
return $this->loadFile($file);
1289
+
}
1290
+
1291
+
return true;
1292
+
}
1293
+
1294
+
/**
1295
+
* Check if passed info might be delimiter.
1296
+
* Only used by find_delimiter
1297
+
*
1298
+
* @param string $char Potential field separating character
1299
+
* @param array $array Frequency
1300
+
* @param int $depth Number of analyzed rows
1301
+
* @param string $preferred Preferred delimiter characters
1302
+
*
1303
+
* @return string|false special string used for delimiter selection, or false
1304
+
*/
1305
+
protected function _check_count($char, $array, $depth, $preferred) {
1306
+
if ($depth === count($array)) {
1307
+
$first = null;
1308
+
$equal = null;
1309
+
$almost = false;
1310
+
foreach ($array as $value) {
1311
+
if ($first == null) {
1312
+
$first = $value;
1313
+
} elseif ($value == $first && $equal !== false) {
1314
+
$equal = true;
1315
+
} elseif ($value == $first + 1 && $equal !== false) {
1316
+
$equal = true;
1317
+
$almost = true;
1318
+
} else {
1319
+
$equal = false;
1320
+
}
1321
+
}
1322
+
1323
+
if ($equal || $depth === 1) {
1324
+
$match = $almost ? 2 : 1;
1325
+
$pref = strpos($preferred, $char);
1326
+
$pref = ($pref !== false) ? str_pad($pref, 3, '0', STR_PAD_LEFT) : '999';
1327
+
1328
+
return $pref . $match . '.' . (99999 - str_pad($first, 5, '0', STR_PAD_LEFT));
1329
+
} else {
1330
+
return false;
1331
+
}
1332
+
}
1333
+
return false;
1334
+
}
1335
+
1336
+
/**
1337
+
* Read local file.
1338
+
*
1339
+
* @param string $filePath local filename
1340
+
*
1341
+
* @return string|false Data from file, or false on failure
1342
+
*/
1343
+
protected function _rfile($filePath) {
1344
+
if (is_readable($filePath)) {
1345
+
$data = file_get_contents($filePath);
1346
+
if ($data === false) {
1347
+
return false;
1348
+
}
1349
+
1350
+
if (preg_match('/\.php$/i', $filePath) && preg_match('/<\?.*?\?>(.*)/ms', $data, $strip)) {
1351
+
// Return section behind closing tags.
1352
+
// This parsing is deprecated and will be removed in v2.0.0.
1353
+
$data = ltrim($strip[1]);
1354
+
}
1355
+
1356
+
return rtrim($data, "\r\n");
1357
+
}
1358
+
1359
+
return false;
1360
+
}
1361
+
1362
+
/**
1363
+
* Write to local file
1364
+
*
1365
+
* @param string $file local filename
1366
+
* @param string $content data to write to file
1367
+
* @param string $mode fopen() mode
1368
+
* @param int $lock flock() mode
1369
+
*
1370
+
* @return bool
1371
+
* True on success
1372
+
*
1373
+
*/
1374
+
protected function _wfile($file, $content = '', $mode = 'wb', $lock = LOCK_EX) {
1375
+
if ($fp = fopen($file, $mode)) {
1376
+
flock($fp, $lock);
1377
+
$re = fwrite($fp, $content);
1378
+
$re2 = fclose($fp);
1379
+
1380
+
if ($re !== false && $re2 !== false) {
1381
+
return true;
1382
+
}
1383
+
}
1384
+
1385
+
return false;
1386
+
}
1387
+
1388
+
/**
1389
+
* Detect separator using a nonstandard hack: such file starts with the
1390
+
* first line containing only "sep=;", where the last character is the
1391
+
* separator. Microsoft Excel is able to open such files.
1392
+
*
1393
+
* @param string $data file data
1394
+
*
1395
+
* @return string|false detected delimiter, or false if none found
1396
+
*/
1397
+
protected function _get_delimiter_from_sep_row($data) {
1398
+
$sep = false;
1399
+
// 32 bytes should be quite enough data for our sniffing, chosen arbitrarily
1400
+
$sepPrefix = substr($data, 0, 32);
1401
+
if (preg_match('/^sep=(.)\\r?\\n/i', $sepPrefix, $sepMatch)) {
1402
+
// we get separator.
1403
+
$sep = $sepMatch[1];
1404
+
}
1405
+
return $sep;
1406
+
}
1407
+
1408
+
/**
1409
+
* Support for Excel-compatible sep=? row.
1410
+
*
1411
+
* @param string $data_string file data to be updated
1412
+
*
1413
+
* @return bool TRUE if sep= line was found at the very beginning of the file
1414
+
*/
1415
+
protected function _detect_and_remove_sep_row_from_data(&$data_string) {
1416
+
$sep = $this->_get_delimiter_from_sep_row($data_string);
1417
+
if ($sep === false) {
1418
+
return false;
1419
+
}
1420
+
1421
+
$this->delimiter = $sep;
1422
+
1423
+
// likely to be 5, but let's not assume we're always single-byte.
1424
+
$pos = 4 + strlen($sep);
1425
+
// the next characters should be a line-end
1426
+
if (substr($data_string, $pos, 1) === "\r") {
1427
+
$pos++;
1428
+
}
1429
+
if (substr($data_string, $pos, 1) === "\n") {
1430
+
$pos++;
1431
+
}
1432
+
1433
+
// remove delimiter and its line-end (the data param is by-ref!)
1434
+
$data_string = substr($data_string, $pos);
1435
+
return true;
1436
+
}
1437
+
1438
+
/**
1439
+
* @param int $search_depth Number of rows to analyze
1440
+
* @param string $preferred Preferred delimiter characters
1441
+
* @param string $enclosure Enclosure character, default is double quote
1442
+
* @param string $data The file content
1443
+
*/
1444
+
protected function _guess_delimiter($search_depth, $preferred, $enclosure, $data) {
1445
+
$chars = [];
1446
+
$strlen = strlen($data);
1447
+
$enclosed = false;
1448
+
$current_row = 1;
1449
+
$to_end = true;
1450
+
1451
+
// The dash is the only character we don't want quoted, as it would
1452
+
// prevent character ranges within $auto_non_chars:
1453
+
$quoted_auto_non_chars = preg_quote($this->auto_non_chars, '/');
1454
+
$quoted_auto_non_chars = str_replace('\-', '-', $quoted_auto_non_chars);
1455
+
$pattern = '/[' . $quoted_auto_non_chars . ']/i';
1456
+
1457
+
// walk specific depth finding possible delimiter characters
1458
+
for ($i = 0; $i < $strlen; $i++) {
1459
+
$ch = $data[$i];
1460
+
$nch = isset($data[$i + 1]) ? $data[$i + 1] : false;
1461
+
$pch = isset($data[$i - 1]) ? $data[$i - 1] : false;
1462
+
1463
+
// open and closing quotes
1464
+
$is_newline = ($ch == "\n" && $pch != "\r") || $ch == "\r";
1465
+
if ($ch == $enclosure) {
1466
+
if (!$enclosed || $nch != $enclosure) {
1467
+
$enclosed = !$enclosed;
1468
+
} elseif ($enclosed) {
1469
+
$i++;
1470
+
}
1471
+
1472
+
// end of row
1473
+
} elseif ($is_newline && !$enclosed) {
1474
+
if ($current_row >= $search_depth) {
1475
+
$strlen = 0;
1476
+
$to_end = false;
1477
+
} else {
1478
+
$current_row++;
1479
+
}
1480
+
1481
+
// count character
1482
+
} elseif (!$enclosed) {
1483
+
if (!preg_match($pattern, $ch)) {
1484
+
if (!isset($chars[$ch][$current_row])) {
1485
+
$chars[$ch][$current_row] = 1;
1486
+
} else {
1487
+
$chars[$ch][$current_row]++;
1488
+
}
1489
+
}
1490
+
}
1491
+
}
1492
+
1493
+
// filtering
1494
+
$depth = $to_end ? $current_row - 1 : $current_row;
1495
+
$filtered = [];
1496
+
foreach ($chars as $char => $value) {
1497
+
if(!in_array($char, $this->auto_banned))
1498
+
{
1499
+
if ($match = $this->_check_count($char, $value, $depth, $preferred)) {
1500
+
$filtered[$match] = $char;
1501
+
}
1502
+
}
1503
+
}
1504
+
if(empty($filtered))
1505
+
{
1506
+
$filtered = str_split($this->auto_preferred);
1507
+
}
1508
+
// capture most probable delimiter
1509
+
ksort($filtered);
1510
+
$this->delimiter = reset($filtered);
1511
+
}
1512
+
1513
+
/**
1514
+
* getCollection
1515
+
* Returns a Illuminate/Collection object
1516
+
* This may prove to be helpful to people who want to
1517
+
* create macros, and or use map functions
1518
+
*
1519
+
* @access public
1520
+
* @link https://laravel.com/docs/5.6/collections
1521
+
*
1522
+
* @throws \ErrorException - If the Illuminate\Support\Collection class is not found
1523
+
*
1524
+
* @return Collection
1525
+
*/
1526
+
public function getCollection() {
1527
+
//does the Illuminate\Support\Collection class exists?
1528
+
//this uses the autoloader to try to determine
1529
+
//@see http://php.net/manual/en/function.class-exists.php
1530
+
if (class_exists('Illuminate\Support\Collection', true) == false) {
1531
+
throw new \ErrorException('It would appear you have not installed the illuminate/support package!');
1532
+
}
1533
+
1534
+
//return the collection
1535
+
return new Collection($this->data);
1536
+
}
1537
+
}
1538
+