* @version 0.0.0 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.ship-simu.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ class CsvInputFile extends BaseInputTextFile implements CsvInputStreamer { /** * Protected constructor * * @return void */ private function __construct () { // Call parent constructor parent::__construct(__CLASS__); } /** * Creates an instance of this File class and prepares it for usage * * @param $infoInstance An instance of a SplFileInfo class * @return $fileInstance An instance of this File class */ public final static function createCsvInputFile (SplFileInfo $infoInstance) { // Get a new instance $fileInstance = new CsvInputFile(); // Init this abstract file $fileInstance->initFile($infoInstance); // Return the prepared instance return $fileInstance; } /** * Reads a line from CSV file and returns it as an indexed array. Please * note that strings *must* be always in double-quotes, else any found * column separators will be parsed or they may be interpreted incorrectly. * * @param $columnSeparator Character to use separting columns * @param $expectedMatches Expected matches, 0 is default and means flexible * @return $lineArray An indexed array with the read line * @throws InvalidArgumentException If a parameter is invalid * @throws UnexpectedValueException If the array count is not matching expected count */ public function readCsvFileLine (string $columnSeparator, int $expectedMatches = 0) { // Validate parameter /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] columnSeparator=%s,expectedMatches=%d - CALLED!', __METHOD__, __LINE__, $columnSeparator, $expectedMatches)); if (strlen($columnSeparator) === 0) { // No empty column separator throw new InvalidArgumentException('Parameter "columnSeparator" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT); } elseif ($expectedMatches < 0) { // Below zero is not valid throw new InvalidArgumentException(sprintf('expectedMatches=%d is below zero', $expectedMatches)); } // Read raw line and trim anything unwanted away $data = trim($this->getPointerInstance()->readLine()); // Is the line empty? /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] data(%d)=%s', __METHOD__, __LINE__, strlen($data), $data)); if (empty($data)) { // Yes, then skip below code /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Read data is an empty line - EXIT!', __METHOD__, __LINE__)); return; } // Parse data $lineArray = $this->parseDataToIndexedArray($data, $columnSeparator); // Is the expected count found? /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] expectedMatches=%d,lineArray()=%d', __METHOD__, __LINE__, $expectedMatches, count($lineArray))); if (($expectedMatches > 0) && (count($lineArray) !== $expectedMatches)) { // Invalid line found as strict count matching is requested throw new UnexpectedValueException(sprintf('lineArray()=%d has not expected count %d', count($lineArray), $expectedMatches), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE); } // Return it /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] lineArray()=%d - EXIT!', __METHOD__, __LINE__, count($lineArray))); return $lineArray; } /** * Parses given data into an array * * @param $data Raw data e.g. returned from readLine() * @param $columnSeparator Character to use separting columns * @return $lineArray An indexed array with the read line */ private function parseDataToIndexedArray (string $data, string $columnSeparator) { // Init return array //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] data()=%d,columnSeparator=%s - CALLED!', __METHOD__, __LINE__, strlen($data), $columnSeparator)); $lineArray = []; // Whether the parser reads a quoted string (which may contain the column separator again) $isInQuotes = false; // Init column data $column = ''; // Now parse the line for ($idx = 0; $idx < strlen($data); $idx++) { // "Cache" char $char = substr($data, $idx, 1); // Is the column separator found and not within quotes? //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] idx=%d,char=%s ...', __METHOD__, __LINE__, $idx, $char)); if (($isInQuotes === false) && ($char == $columnSeparator)) { // Add this line to the array //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Adding column=%s ...', __METHOD__, __LINE__, $column)); array_push($lineArray, $column); // Clear variable ... //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] line[]=%d - After add!', __METHOD__, __LINE__, count($lineArray))); $column = ''; // ... and skip it continue; } elseif ($char == chr(34)) { // $column must be empty at this point if we are at starting quote //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] column=%s ...', __METHOD__, __LINE__, $column)); assert(($isInQuotes === true) || (empty($column))); // Double-quote found, so flip variable $isInQuotes = (!$isInQuotes); // Skip double-quote (escaping of them is not yet supported) //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] isInQuotes=%d ...', __METHOD__, __LINE__, intval($isInQuotes))); continue; } // Add char to column //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Adding char=%s ...', __METHOD__, __LINE__, $idx, $char)); $column .= $char; } // Is there something outstanding? if (!empty($column)) { // Then don't forget this. :-) //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Adding column=%s ...', __METHOD__, __LINE__, $column)); array_push($lineArray, $column); // Debug message //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] line[]=%d - After add!', __METHOD__, __LINE__, count($lineArray))); } // Return it //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] line[]=%d - EXIT!', __METHOD__, __LINE__, count($lineArray))); return $lineArray; } }