Polyfill
10 months ago
AbstractCsv.php
10 months ago
ByteSequence.php
10 months ago
CannotInsertRecord.php
10 months ago
CharsetConverter.php
10 months ago
ColumnConsistency.php
10 months ago
EncloseField.php
10 months ago
EscapeFormula.php
10 months ago
Exception.php
10 months ago
HTMLConverter.php
10 months ago
Info.php
10 months ago
InvalidArgument.php
10 months ago
MapIterator.php
10 months ago
RFC4180Field.php
10 months ago
Reader.php
10 months ago
ResultSet.php
10 months ago
Statement.php
10 months ago
Stream.php
10 months ago
SyntaxError.php
10 months ago
TabularDataReader.php
10 months ago
UnableToProcessCsv.php
10 months ago
UnavailableFeature.php
10 months ago
UnavailableStream.php
10 months ago
Writer.php
10 months ago
XMLConverter.php
10 months ago
functions.php
10 months ago
functions_include.php
10 months ago
Stream.php
448 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * League.Csv (https://csv.thephpleague.com) |
| 5 | * |
| 6 | * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> |
| 7 | * |
| 8 | * For the full copyright and license information, please view the LICENSE |
| 9 | * file that was distributed with this source code. |
| 10 | */ |
| 11 | declare (strict_types=1); |
| 12 | namespace IAWPSCOPED\League\Csv; |
| 13 | |
| 14 | use ReturnTypeWillChange; |
| 15 | use SeekableIterator; |
| 16 | use SplFileObject; |
| 17 | use TypeError; |
| 18 | use function array_keys; |
| 19 | use function array_walk_recursive; |
| 20 | use function fclose; |
| 21 | use function feof; |
| 22 | use function fflush; |
| 23 | use function fgetcsv; |
| 24 | use function fgets; |
| 25 | use function fopen; |
| 26 | use function fpassthru; |
| 27 | use function fputcsv; |
| 28 | use function fread; |
| 29 | use function fseek; |
| 30 | use function fwrite; |
| 31 | use function get_resource_type; |
| 32 | use function gettype; |
| 33 | use function is_array; |
| 34 | use function is_resource; |
| 35 | use function rewind; |
| 36 | use function stream_filter_append; |
| 37 | use function stream_filter_remove; |
| 38 | use function stream_get_meta_data; |
| 39 | use function strlen; |
| 40 | use const PHP_VERSION_ID; |
| 41 | use const SEEK_SET; |
| 42 | /** |
| 43 | * An object oriented API to handle a PHP stream resource. |
| 44 | * |
| 45 | * @internal used internally to iterate over a stream resource |
| 46 | */ |
| 47 | final class Stream implements SeekableIterator |
| 48 | { |
| 49 | /** |
| 50 | * Attached filters. |
| 51 | * |
| 52 | * @var array<string, array<resource>> |
| 53 | */ |
| 54 | private $filters = []; |
| 55 | /** |
| 56 | * stream resource. |
| 57 | * |
| 58 | * @var resource |
| 59 | */ |
| 60 | private $stream; |
| 61 | /** |
| 62 | * Tell whether the stream should be closed on object destruction. |
| 63 | * |
| 64 | * @var bool |
| 65 | */ |
| 66 | private $should_close_stream = \false; |
| 67 | /** |
| 68 | * Current iterator value. |
| 69 | * |
| 70 | * @var mixed can be a null false or a scalar type value |
| 71 | */ |
| 72 | private $value; |
| 73 | /** |
| 74 | * Current iterator key. |
| 75 | * |
| 76 | * @var int |
| 77 | */ |
| 78 | private $offset; |
| 79 | /** |
| 80 | * Flags for the Document. |
| 81 | * |
| 82 | * @var int |
| 83 | */ |
| 84 | private $flags = 0; |
| 85 | /** |
| 86 | * the field delimiter (one character only). |
| 87 | * |
| 88 | * @var string |
| 89 | */ |
| 90 | private $delimiter = ','; |
| 91 | /** |
| 92 | * the field enclosure character (one character only). |
| 93 | * |
| 94 | * @var string |
| 95 | */ |
| 96 | private $enclosure = '"'; |
| 97 | /** |
| 98 | * the field escape character (one character only). |
| 99 | * |
| 100 | * @var string |
| 101 | */ |
| 102 | private $escape = '\\'; |
| 103 | /** |
| 104 | * Tell whether the current stream is seekable;. |
| 105 | * |
| 106 | * @var bool |
| 107 | */ |
| 108 | private $is_seekable = \false; |
| 109 | /** |
| 110 | * New instance. |
| 111 | * |
| 112 | * @param mixed $stream stream type resource |
| 113 | */ |
| 114 | public function __construct($stream) |
| 115 | { |
| 116 | if (!is_resource($stream)) { |
| 117 | throw new TypeError('Argument passed must be a stream resource, ' . gettype($stream) . ' given.'); |
| 118 | } |
| 119 | if ('stream' !== ($type = get_resource_type($stream))) { |
| 120 | throw new TypeError('Argument passed must be a stream resource, ' . $type . ' resource given'); |
| 121 | } |
| 122 | $this->is_seekable = stream_get_meta_data($stream)['seekable']; |
| 123 | $this->stream = $stream; |
| 124 | } |
| 125 | /** |
| 126 | * {@inheritdoc} |
| 127 | */ |
| 128 | public function __destruct() |
| 129 | { |
| 130 | $walker = static function ($filter) : bool { |
| 131 | return @stream_filter_remove($filter); |
| 132 | }; |
| 133 | array_walk_recursive($this->filters, $walker); |
| 134 | if ($this->should_close_stream && is_resource($this->stream)) { |
| 135 | fclose($this->stream); |
| 136 | } |
| 137 | unset($this->stream); |
| 138 | } |
| 139 | /** |
| 140 | * {@inheritdoc} |
| 141 | */ |
| 142 | public function __clone() |
| 143 | { |
| 144 | throw UnavailableStream::dueToForbiddenCloning(self::class); |
| 145 | } |
| 146 | /** |
| 147 | * {@inheritdoc} |
| 148 | */ |
| 149 | public function __debugInfo() : array |
| 150 | { |
| 151 | return stream_get_meta_data($this->stream) + ['delimiter' => $this->delimiter, 'enclosure' => $this->enclosure, 'escape' => $this->escape, 'stream_filters' => array_keys($this->filters)]; |
| 152 | } |
| 153 | /** |
| 154 | * Return a new instance from a file path. |
| 155 | * |
| 156 | * @param resource|null $context |
| 157 | * |
| 158 | * @throws Exception if the stream resource can not be created |
| 159 | */ |
| 160 | public static function createFromPath(string $path, string $open_mode = 'r', $context = null) : self |
| 161 | { |
| 162 | $args = [$path, $open_mode]; |
| 163 | if (null !== $context) { |
| 164 | $args[] = \false; |
| 165 | $args[] = $context; |
| 166 | } |
| 167 | $resource = @fopen(...$args); |
| 168 | if (!is_resource($resource)) { |
| 169 | throw UnavailableStream::dueToPathNotFound($path); |
| 170 | } |
| 171 | $instance = new self($resource); |
| 172 | $instance->should_close_stream = \true; |
| 173 | return $instance; |
| 174 | } |
| 175 | /** |
| 176 | * Return a new instance from a string. |
| 177 | */ |
| 178 | public static function createFromString(string $content = '') : self |
| 179 | { |
| 180 | /** @var resource $resource */ |
| 181 | $resource = fopen('php://temp', 'r+'); |
| 182 | fwrite($resource, $content); |
| 183 | $instance = new self($resource); |
| 184 | $instance->should_close_stream = \true; |
| 185 | return $instance; |
| 186 | } |
| 187 | /** |
| 188 | * Return the URI of the underlying stream. |
| 189 | */ |
| 190 | public function getPathname() : string |
| 191 | { |
| 192 | return stream_get_meta_data($this->stream)['uri']; |
| 193 | } |
| 194 | /** |
| 195 | * append a filter. |
| 196 | * |
| 197 | * @see http://php.net/manual/en/function.stream-filter-append.php |
| 198 | * |
| 199 | * |
| 200 | * @throws InvalidArgument if the filter can not be appended |
| 201 | */ |
| 202 | public function appendFilter(string $filtername, int $read_write, array $params = null) : void |
| 203 | { |
| 204 | $res = @stream_filter_append($this->stream, $filtername, $read_write, $params ?? []); |
| 205 | if (!is_resource($res)) { |
| 206 | throw InvalidArgument::dueToStreamFilterNotFound($filtername); |
| 207 | } |
| 208 | $this->filters[$filtername][] = $res; |
| 209 | } |
| 210 | /** |
| 211 | * Set CSV control. |
| 212 | * |
| 213 | * @see http://php.net/manual/en/SplFileObject.setcsvcontrol.php |
| 214 | */ |
| 215 | public function setCsvControl(string $delimiter = ',', string $enclosure = '"', string $escape = '\\') : void |
| 216 | { |
| 217 | list($this->delimiter, $this->enclosure, $this->escape) = $this->filterControl($delimiter, $enclosure, $escape, __METHOD__); |
| 218 | } |
| 219 | /** |
| 220 | * Filter Csv control characters. |
| 221 | * |
| 222 | * @throws InvalidArgument If the Csv control character is not one character only. |
| 223 | */ |
| 224 | private function filterControl(string $delimiter, string $enclosure, string $escape, string $caller) : array |
| 225 | { |
| 226 | if (1 !== strlen($delimiter)) { |
| 227 | throw InvalidArgument::dueToInvalidDelimiterCharacter($delimiter, $caller); |
| 228 | } |
| 229 | if (1 !== strlen($enclosure)) { |
| 230 | throw InvalidArgument::dueToInvalidEnclosureCharacter($enclosure, $caller); |
| 231 | } |
| 232 | if (1 === strlen($escape) || '' === $escape && 70400 <= PHP_VERSION_ID) { |
| 233 | return [$delimiter, $enclosure, $escape]; |
| 234 | } |
| 235 | throw InvalidArgument::dueToInvalidEscapeCharacter($escape, $caller); |
| 236 | } |
| 237 | /** |
| 238 | * Set CSV control. |
| 239 | * |
| 240 | * @see http://php.net/manual/en/SplFileObject.getcsvcontrol.php |
| 241 | * |
| 242 | * @return array<string> |
| 243 | */ |
| 244 | public function getCsvControl() : array |
| 245 | { |
| 246 | return [$this->delimiter, $this->enclosure, $this->escape]; |
| 247 | } |
| 248 | /** |
| 249 | * Set CSV stream flags. |
| 250 | * |
| 251 | * @see http://php.net/manual/en/SplFileObject.setflags.php |
| 252 | */ |
| 253 | public function setFlags(int $flags) : void |
| 254 | { |
| 255 | $this->flags = $flags; |
| 256 | } |
| 257 | /** |
| 258 | * Write a field array as a CSV line. |
| 259 | * |
| 260 | * @see http://php.net/manual/en/SplFileObject.fputcsv.php |
| 261 | * |
| 262 | * @return int|false |
| 263 | */ |
| 264 | public function fputcsv(array $fields, string $delimiter = ',', string $enclosure = '"', string $escape = '\\', string $eol = "\n") |
| 265 | { |
| 266 | $controls = $this->filterControl($delimiter, $enclosure, $escape, __METHOD__); |
| 267 | if (80100 <= PHP_VERSION_ID) { |
| 268 | $controls[] = $eol; |
| 269 | } |
| 270 | return fputcsv($this->stream, $fields, ...$controls); |
| 271 | } |
| 272 | /** |
| 273 | * Get line number. |
| 274 | * |
| 275 | * @see http://php.net/manual/en/SplFileObject.key.php |
| 276 | */ |
| 277 | public function key() : int |
| 278 | { |
| 279 | return $this->offset; |
| 280 | } |
| 281 | /** |
| 282 | * Read next line. |
| 283 | * |
| 284 | * @see http://php.net/manual/en/SplFileObject.next.php |
| 285 | */ |
| 286 | public function next() : void |
| 287 | { |
| 288 | $this->value = \false; |
| 289 | $this->offset++; |
| 290 | } |
| 291 | /** |
| 292 | * Rewind the file to the first line. |
| 293 | * |
| 294 | * @see http://php.net/manual/en/SplFileObject.rewind.php |
| 295 | * |
| 296 | * @throws Exception if the stream resource is not seekable |
| 297 | */ |
| 298 | public function rewind() : void |
| 299 | { |
| 300 | if (!$this->is_seekable) { |
| 301 | throw UnavailableFeature::dueToMissingStreamSeekability(); |
| 302 | } |
| 303 | rewind($this->stream); |
| 304 | $this->offset = 0; |
| 305 | $this->value = \false; |
| 306 | if (0 !== ($this->flags & SplFileObject::READ_AHEAD)) { |
| 307 | $this->current(); |
| 308 | } |
| 309 | } |
| 310 | /** |
| 311 | * Not at EOF. |
| 312 | * |
| 313 | * @see http://php.net/manual/en/SplFileObject.valid.php |
| 314 | */ |
| 315 | public function valid() : bool |
| 316 | { |
| 317 | if (0 !== ($this->flags & SplFileObject::READ_AHEAD)) { |
| 318 | return $this->current() !== \false; |
| 319 | } |
| 320 | return !feof($this->stream); |
| 321 | } |
| 322 | /** |
| 323 | * Retrieves the current line of the file. |
| 324 | * |
| 325 | * @see http://php.net/manual/en/SplFileObject.current.php |
| 326 | * |
| 327 | * @return mixed The value of the current element. |
| 328 | */ |
| 329 | #[ReturnTypeWillChange] |
| 330 | public function current() |
| 331 | { |
| 332 | if (\false !== $this->value) { |
| 333 | return $this->value; |
| 334 | } |
| 335 | $this->value = $this->getCurrentRecord(); |
| 336 | return $this->value; |
| 337 | } |
| 338 | /** |
| 339 | * Retrieves the current line as a CSV Record. |
| 340 | * |
| 341 | * @return array|false |
| 342 | */ |
| 343 | private function getCurrentRecord() |
| 344 | { |
| 345 | $flag = 0 !== ($this->flags & SplFileObject::SKIP_EMPTY); |
| 346 | do { |
| 347 | $ret = fgetcsv($this->stream, 0, $this->delimiter, $this->enclosure, $this->escape); |
| 348 | } while ($flag && is_array($ret) && null === $ret[0]); |
| 349 | return $ret; |
| 350 | } |
| 351 | /** |
| 352 | * Seek to specified line. |
| 353 | * |
| 354 | * @see http://php.net/manual/en/SplFileObject.seek.php |
| 355 | * |
| 356 | * @param int $position |
| 357 | * @throws Exception if the position is negative |
| 358 | */ |
| 359 | public function seek($position) : void |
| 360 | { |
| 361 | if ($position < 0) { |
| 362 | throw InvalidArgument::dueToInvalidSeekingPosition($position, __METHOD__); |
| 363 | } |
| 364 | $this->rewind(); |
| 365 | while ($this->key() !== $position && $this->valid()) { |
| 366 | $this->current(); |
| 367 | $this->next(); |
| 368 | } |
| 369 | if (0 !== $position) { |
| 370 | $this->offset--; |
| 371 | } |
| 372 | $this->current(); |
| 373 | } |
| 374 | /** |
| 375 | * Output all remaining data on a file pointer. |
| 376 | * |
| 377 | * @see http://php.net/manual/en/SplFileObject.fpatssthru.php |
| 378 | * |
| 379 | * @return int|false |
| 380 | */ |
| 381 | public function fpassthru() |
| 382 | { |
| 383 | return fpassthru($this->stream); |
| 384 | } |
| 385 | /** |
| 386 | * Read from file. |
| 387 | * |
| 388 | * @see http://php.net/manual/en/SplFileObject.fread.php |
| 389 | * |
| 390 | * @param int<0, max> $length The number of bytes to read |
| 391 | * |
| 392 | * @return string|false |
| 393 | */ |
| 394 | public function fread(int $length) |
| 395 | { |
| 396 | return fread($this->stream, $length); |
| 397 | } |
| 398 | /** |
| 399 | * Gets a line from file. |
| 400 | * |
| 401 | * @see http://php.net/manual/en/SplFileObject.fgets.php |
| 402 | * |
| 403 | * @return string|false |
| 404 | */ |
| 405 | public function fgets() |
| 406 | { |
| 407 | return fgets($this->stream); |
| 408 | } |
| 409 | /** |
| 410 | * Seek to a position. |
| 411 | * |
| 412 | * @see http://php.net/manual/en/SplFileObject.fseek.php |
| 413 | * |
| 414 | * @throws Exception if the stream resource is not seekable |
| 415 | */ |
| 416 | public function fseek(int $offset, int $whence = SEEK_SET) : int |
| 417 | { |
| 418 | if (!$this->is_seekable) { |
| 419 | throw UnavailableFeature::dueToMissingStreamSeekability(); |
| 420 | } |
| 421 | return fseek($this->stream, $offset, $whence); |
| 422 | } |
| 423 | /** |
| 424 | * Write to stream. |
| 425 | * |
| 426 | * @see http://php.net/manual/en/SplFileObject.fwrite.php |
| 427 | * |
| 428 | * @return int|false |
| 429 | */ |
| 430 | public function fwrite(string $str, int $length = null) |
| 431 | { |
| 432 | $args = [$this->stream, $str]; |
| 433 | if (null !== $length) { |
| 434 | $args[] = $length; |
| 435 | } |
| 436 | return fwrite(...$args); |
| 437 | } |
| 438 | /** |
| 439 | * Flushes the output to a file. |
| 440 | * |
| 441 | * @see http://php.net/manual/en/SplFileObject.fwrite.php |
| 442 | */ |
| 443 | public function fflush() : bool |
| 444 | { |
| 445 | return fflush($this->stream); |
| 446 | } |
| 447 | } |
| 448 |