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