Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/aimogen-pro/res/parsecsv/src/Csv.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
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 +