]> git.mxchange.org Git - core.git/blob - framework/main/classes/file_directories/text/input/csv/class_CsvInputFile.php
Continued:
[core.git] / framework / main / classes / file_directories / text / input / csv / class_CsvInputFile.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Filesystem\Input\Csv;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Filesystem\Text\BaseInputTextFile;
7 use Org\Mxchange\CoreFramework\Stream\Filesystem\CsvInputStreamer;
8
9 // Import SPL stuff
10 use \InvalidArgumentException;
11 use \SplFileInfo;
12 use \UnexpectedValueException;
13
14 /**
15  * A CSV file input class for writing CSV files
16  *
17  * @author              Roland Haeder <webmaster@ship-simu.org>
18  * @version             0.0.0
19  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2021 Core Developer Team
20  * @license             GNU GPL 3.0 or any newer version
21  * @link                http://www.ship-simu.org
22  *
23  * This program is free software: you can redistribute it and/or modify
24  * it under the terms of the GNU General Public License as published by
25  * the Free Software Foundation, either version 3 of the License, or
26  * (at your option) any later version.
27  *
28  * This program is distributed in the hope that it will be useful,
29  * but WITHOUT ANY WARRANTY; without even the implied warranty of
30  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31  * GNU General Public License for more details.
32  *
33  * You should have received a copy of the GNU General Public License
34  * along with this program. If not, see <http://www.gnu.org/licenses/>.
35  */
36 class CsvInputFile extends BaseInputTextFile implements CsvInputStreamer {
37         /**
38          * Protected constructor
39          *
40          * @return      void
41          */
42         private function __construct () {
43                 // Call parent constructor
44                 parent::__construct(__CLASS__);
45         }
46
47         /**
48          * Creates an instance of this File class and prepares it for usage
49          *
50          * @param       $infoInstance   An instance of a SplFileInfo class
51          * @return      $fileInstance   An instance of this File class
52          */
53         public final static function createCsvInputFile (SplFileInfo $infoInstance) {
54                 // Get a new instance
55                 $fileInstance = new CsvInputFile();
56
57                 // Init this abstract file
58                 $fileInstance->initFile($infoInstance);
59
60                 // Return the prepared instance
61                 return $fileInstance;
62         }
63
64         /**
65          * Reads a line from CSV file and returns it as an indexed array. Please
66          * note that strings *must* be always in double-quotes, else any found
67          * column separators will be parsed or they may be interpreted incorrectly.
68          *
69          * @param       $columnSeparator        Character to use separting columns
70          * @param       $expectedMatches        Expected matches, 0 is default and means flexible
71          * @return      $lineArray                      An indexed array with the read line
72          * @throws      InvalidArgumentException        If a parameter is invalid
73          * @throws      UnexpectedValueException        If the array count is not matching expected count
74          */
75         public function readCsvFileLine (string $columnSeparator, int $expectedMatches = 0) {
76                 // Validate parameter
77                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] columnSeparator=%s,expectedMatches=%d - CALLED!', __METHOD__, __LINE__, $columnSeparator, $expectedMatches));
78                 if (strlen($columnSeparator) === 0) {
79                         // No empty column separator
80                         throw new InvalidArgumentException('columnSeparator cannot be empty.');
81                 } elseif ($expectedMatches < 0) {
82                         // Below zero is not valid
83                         throw new InvalidArgumentException(sprintf('expectedMatches=%d is below zero', $expectedMatches));
84                 }
85
86                 // Read raw line and trim anything unwanted away
87                 $data = trim($this->getPointerInstance()->readLine());
88
89                 // Is the line empty?
90                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] data(%d)=%s', __METHOD__, __LINE__, strlen($data), $data));
91                 if (empty($data)) {
92                         // Yes, then skip below code
93                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Read data is an empty line - EXIT!', __METHOD__, __LINE__));
94                         return;
95                 }
96
97                 // Parse data
98                 $lineArray = $this->parseDataToIndexedArray($data, $columnSeparator);
99
100                 // Is the expected count found?
101                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] expectedMatches=%d,lineArray()=%d', __METHOD__, __LINE__, $expectedMatches, count($lineArray)));
102                 if (($expectedMatches > 0) && (count($lineArray) !== $expectedMatches)) {
103                         // Invalid line found as strict count matching is requested
104                         throw new UnexpectedValueException(sprintf('lineArray()=%d has not expected count %d', count($lineArray), $expectedMatches));
105                 }
106
107                 // Return it
108                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] lineArray()=%d - EXIT!', __METHOD__, __LINE__, count($lineArray)));
109                 return $lineArray;
110         }
111
112         /**
113          * Parses given data into an array
114          *
115          * @param       $data                           Raw data e.g. returned from readLine()
116          * @param       $columnSeparator        Character to use separting columns
117          * @return      $lineArray                      An indexed array with the read line
118          */
119         private function parseDataToIndexedArray (string $data, string $columnSeparator) {
120                 // Init return array
121                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] data()=%d,columnSeparator=%s - CALLED!', __METHOD__, __LINE__, strlen($data), $columnSeparator));
122                 $lineArray = [];
123
124                 // Whether the parser reads a quoted string (which may contain the column separator again)
125                 $isInQuotes = false;
126
127                 // Init column data
128                 $column = '';
129
130                 // Now parse the line
131                 for ($idx = 0; $idx < strlen($data); $idx++) {
132                         // "Cache" char
133                         $char = substr($data, $idx, 1);
134
135                         // Is the column separator found and not within quotes?
136                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] idx=%d,char=%s ...', __METHOD__, __LINE__, $idx, $char));
137                         if (($isInQuotes === false) && ($char == $columnSeparator)) {
138                                 // Add this line to the array
139                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Adding column=%s ...', __METHOD__, __LINE__, $column));
140                                 array_push($lineArray, $column);
141
142                                 // Clear variable ...
143                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] line[]=%d - After add!', __METHOD__, __LINE__, count($lineArray)));
144                                 $column = '';
145
146                                 // ... and skip it
147                                 continue;
148                         } elseif ($char == chr(34)) {
149                                 // $column must be empty at this point if we are at starting quote
150                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] column=%s ...', __METHOD__, __LINE__, $column));
151                                 assert(($isInQuotes === true) || (empty($column)));
152
153                                 // Double-quote found, so flip variable
154                                 $isInQuotes = (!$isInQuotes);
155
156                                 // Skip double-quote (escaping of them is not yet supported)
157                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] isInQuotes=%d ...', __METHOD__, __LINE__, intval($isInQuotes)));
158                                 continue;
159                         }
160
161                         // Add char to column
162                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Adding char=%s ...', __METHOD__, __LINE__, $idx, $char));
163                         $column .= $char;
164                 }
165
166                 // Is there something outstanding?
167                 if (!empty($column)) {
168                         // Then don't forget this. :-)
169                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Adding column=%s ...', __METHOD__, __LINE__, $column));
170                         array_push($lineArray, $column);
171
172                         // Debug message
173                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] line[]=%d - After add!', __METHOD__, __LINE__, count($lineArray)));
174                 }
175
176                 // Return it
177                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] line[]=%d - EXIT!', __METHOD__, __LINE__, count($lineArray)));
178                 return $lineArray;
179         }
180
181 }
182