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
AbstractCsv.php
473 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 Generator; |
| 15 | use SplFileObject; |
| 16 | use function filter_var; |
| 17 | use function get_class; |
| 18 | use function mb_strlen; |
| 19 | use function rawurlencode; |
| 20 | use function sprintf; |
| 21 | use function str_replace; |
| 22 | use function str_split; |
| 23 | use function strcspn; |
| 24 | use function strlen; |
| 25 | use const FILTER_FLAG_STRIP_HIGH; |
| 26 | use const FILTER_FLAG_STRIP_LOW; |
| 27 | use const FILTER_UNSAFE_RAW; |
| 28 | /** |
| 29 | * An abstract class to enable CSV document loading. |
| 30 | * @internal |
| 31 | */ |
| 32 | abstract class AbstractCsv implements ByteSequence |
| 33 | { |
| 34 | protected const STREAM_FILTER_MODE = \STREAM_FILTER_READ; |
| 35 | /** |
| 36 | * collection of stream filters. |
| 37 | * |
| 38 | * @var bool[] |
| 39 | */ |
| 40 | protected $stream_filters = []; |
| 41 | /** |
| 42 | * The CSV document BOM sequence. |
| 43 | * |
| 44 | * @var string|null |
| 45 | */ |
| 46 | protected $input_bom = null; |
| 47 | /** |
| 48 | * The Output file BOM character. |
| 49 | * |
| 50 | * @var string |
| 51 | */ |
| 52 | protected $output_bom = ''; |
| 53 | /** |
| 54 | * the field delimiter (one character only). |
| 55 | * |
| 56 | * @var string |
| 57 | */ |
| 58 | protected $delimiter = ','; |
| 59 | /** |
| 60 | * the field enclosure character (one character only). |
| 61 | * |
| 62 | * @var string |
| 63 | */ |
| 64 | protected $enclosure = '"'; |
| 65 | /** |
| 66 | * the field escape character (one character only). |
| 67 | * |
| 68 | * @var string |
| 69 | */ |
| 70 | protected $escape = '\\'; |
| 71 | /** |
| 72 | * The CSV document. |
| 73 | * |
| 74 | * @var SplFileObject|Stream |
| 75 | */ |
| 76 | protected $document; |
| 77 | /** |
| 78 | * Tells whether the Input BOM must be included or skipped. |
| 79 | * |
| 80 | * @var bool |
| 81 | */ |
| 82 | protected $is_input_bom_included = \false; |
| 83 | /** |
| 84 | * New instance. |
| 85 | * |
| 86 | * @param SplFileObject|Stream $document The CSV Object instance |
| 87 | */ |
| 88 | protected function __construct($document) |
| 89 | { |
| 90 | $this->document = $document; |
| 91 | [$this->delimiter, $this->enclosure, $this->escape] = $this->document->getCsvControl(); |
| 92 | $this->resetProperties(); |
| 93 | } |
| 94 | /** |
| 95 | * Reset dynamic object properties to improve performance. |
| 96 | */ |
| 97 | protected abstract function resetProperties() : void; |
| 98 | /** |
| 99 | * {@inheritdoc} |
| 100 | */ |
| 101 | public function __destruct() |
| 102 | { |
| 103 | unset($this->document); |
| 104 | } |
| 105 | /** |
| 106 | * {@inheritdoc} |
| 107 | */ |
| 108 | public function __clone() |
| 109 | { |
| 110 | throw UnavailableStream::dueToForbiddenCloning(static::class); |
| 111 | } |
| 112 | /** |
| 113 | * Return a new instance from a SplFileObject. |
| 114 | * |
| 115 | * @return static |
| 116 | */ |
| 117 | public static function createFromFileObject(SplFileObject $file) |
| 118 | { |
| 119 | return new static($file); |
| 120 | } |
| 121 | /** |
| 122 | * Return a new instance from a PHP resource stream. |
| 123 | * |
| 124 | * @param resource $stream |
| 125 | * |
| 126 | * @return static |
| 127 | */ |
| 128 | public static function createFromStream($stream) |
| 129 | { |
| 130 | return new static(new Stream($stream)); |
| 131 | } |
| 132 | /** |
| 133 | * Return a new instance from a string. |
| 134 | * |
| 135 | * @return static |
| 136 | */ |
| 137 | public static function createFromString(string $content = '') |
| 138 | { |
| 139 | return new static(Stream::createFromString($content)); |
| 140 | } |
| 141 | /** |
| 142 | * Return a new instance from a file path. |
| 143 | * |
| 144 | * @param resource|null $context the resource context |
| 145 | * |
| 146 | * @return static |
| 147 | */ |
| 148 | public static function createFromPath(string $path, string $open_mode = 'r+', $context = null) |
| 149 | { |
| 150 | return new static(Stream::createFromPath($path, $open_mode, $context)); |
| 151 | } |
| 152 | /** |
| 153 | * Returns the current field delimiter. |
| 154 | */ |
| 155 | public function getDelimiter() : string |
| 156 | { |
| 157 | return $this->delimiter; |
| 158 | } |
| 159 | /** |
| 160 | * Returns the current field enclosure. |
| 161 | */ |
| 162 | public function getEnclosure() : string |
| 163 | { |
| 164 | return $this->enclosure; |
| 165 | } |
| 166 | /** |
| 167 | * Returns the pathname of the underlying document. |
| 168 | */ |
| 169 | public function getPathname() : string |
| 170 | { |
| 171 | return $this->document->getPathname(); |
| 172 | } |
| 173 | /** |
| 174 | * Returns the current field escape character. |
| 175 | */ |
| 176 | public function getEscape() : string |
| 177 | { |
| 178 | return $this->escape; |
| 179 | } |
| 180 | /** |
| 181 | * Returns the BOM sequence in use on Output methods. |
| 182 | */ |
| 183 | public function getOutputBOM() : string |
| 184 | { |
| 185 | return $this->output_bom; |
| 186 | } |
| 187 | /** |
| 188 | * Returns the BOM sequence of the given CSV. |
| 189 | */ |
| 190 | public function getInputBOM() : string |
| 191 | { |
| 192 | if (null !== $this->input_bom) { |
| 193 | return $this->input_bom; |
| 194 | } |
| 195 | $this->document->setFlags(SplFileObject::READ_CSV); |
| 196 | $this->document->rewind(); |
| 197 | $this->input_bom = Info::fetchBOMSequence((string) $this->document->fread(4)) ?? ''; |
| 198 | return $this->input_bom; |
| 199 | } |
| 200 | /** |
| 201 | * DEPRECATION WARNING! This method will be removed in the next major point release. |
| 202 | * |
| 203 | * @deprecated since version 9.7.0 |
| 204 | * @see AbstractCsv::supportsStreamFilterOnRead |
| 205 | * @see AbstractCsv::supportsStreamFilterOnWrite |
| 206 | * |
| 207 | * Returns the stream filter mode. |
| 208 | */ |
| 209 | public function getStreamFilterMode() : int |
| 210 | { |
| 211 | return static::STREAM_FILTER_MODE; |
| 212 | } |
| 213 | /** |
| 214 | * DEPRECATION WARNING! This method will be removed in the next major point release. |
| 215 | * |
| 216 | * @deprecated since version 9.7.0 |
| 217 | * @see AbstractCsv::supportsStreamFilterOnRead |
| 218 | * @see AbstractCsv::supportsStreamFilterOnWrite |
| 219 | * |
| 220 | * Tells whether the stream filter capabilities can be used. |
| 221 | */ |
| 222 | public function supportsStreamFilter() : bool |
| 223 | { |
| 224 | return $this->document instanceof Stream; |
| 225 | } |
| 226 | /** |
| 227 | * Tells whether the stream filter read capabilities can be used. |
| 228 | */ |
| 229 | public function supportsStreamFilterOnRead() : bool |
| 230 | { |
| 231 | return $this->document instanceof Stream && (static::STREAM_FILTER_MODE & \STREAM_FILTER_READ) === \STREAM_FILTER_READ; |
| 232 | } |
| 233 | /** |
| 234 | * Tells whether the stream filter write capabilities can be used. |
| 235 | */ |
| 236 | public function supportsStreamFilterOnWrite() : bool |
| 237 | { |
| 238 | return $this->document instanceof Stream && (static::STREAM_FILTER_MODE & \STREAM_FILTER_WRITE) === \STREAM_FILTER_WRITE; |
| 239 | } |
| 240 | /** |
| 241 | * Tell whether the specify stream filter is attach to the current stream. |
| 242 | */ |
| 243 | public function hasStreamFilter(string $filtername) : bool |
| 244 | { |
| 245 | return $this->stream_filters[$filtername] ?? \false; |
| 246 | } |
| 247 | /** |
| 248 | * Tells whether the BOM can be stripped if presents. |
| 249 | */ |
| 250 | public function isInputBOMIncluded() : bool |
| 251 | { |
| 252 | return $this->is_input_bom_included; |
| 253 | } |
| 254 | /** |
| 255 | * Returns the CSV document as a Generator of string chunk. |
| 256 | * |
| 257 | * @param int $length number of bytes read |
| 258 | * |
| 259 | * @throws Exception if the number of bytes is lesser than 1 |
| 260 | */ |
| 261 | public function chunk(int $length) : Generator |
| 262 | { |
| 263 | if ($length < 1) { |
| 264 | throw InvalidArgument::dueToInvalidChunkSize($length, __METHOD__); |
| 265 | } |
| 266 | $input_bom = $this->getInputBOM(); |
| 267 | $this->document->rewind(); |
| 268 | $this->document->setFlags(0); |
| 269 | $this->document->fseek(strlen($input_bom)); |
| 270 | /** @var array<int, string> $chunks */ |
| 271 | $chunks = str_split($this->output_bom . $this->document->fread($length), $length); |
| 272 | foreach ($chunks as $chunk) { |
| 273 | (yield $chunk); |
| 274 | } |
| 275 | while ($this->document->valid()) { |
| 276 | (yield $this->document->fread($length)); |
| 277 | } |
| 278 | } |
| 279 | /** |
| 280 | * DEPRECATION WARNING! This method will be removed in the next major point release. |
| 281 | * |
| 282 | * @deprecated since version 9.1.0 |
| 283 | * @see AbstractCsv::toString |
| 284 | * |
| 285 | * Retrieves the CSV content |
| 286 | */ |
| 287 | public function __toString() : string |
| 288 | { |
| 289 | return $this->toString(); |
| 290 | } |
| 291 | /** |
| 292 | * Retrieves the CSV content. |
| 293 | * |
| 294 | * DEPRECATION WARNING! This method will be removed in the next major point release |
| 295 | * |
| 296 | * @deprecated since version 9.7.0 |
| 297 | * @see AbstractCsv::toString |
| 298 | */ |
| 299 | public function getContent() : string |
| 300 | { |
| 301 | return $this->toString(); |
| 302 | } |
| 303 | /** |
| 304 | * Retrieves the CSV content. |
| 305 | * |
| 306 | * @throws Exception If the string representation can not be returned |
| 307 | */ |
| 308 | public function toString() : string |
| 309 | { |
| 310 | $raw = ''; |
| 311 | foreach ($this->chunk(8192) as $chunk) { |
| 312 | $raw .= $chunk; |
| 313 | } |
| 314 | return $raw; |
| 315 | } |
| 316 | /** |
| 317 | * Outputs all data on the CSV file. |
| 318 | * |
| 319 | * @return int Returns the number of characters read from the handle |
| 320 | * and passed through to the output. |
| 321 | */ |
| 322 | public function output(string $filename = null) : int |
| 323 | { |
| 324 | if (null !== $filename) { |
| 325 | $this->sendHeaders($filename); |
| 326 | } |
| 327 | $this->document->rewind(); |
| 328 | if (!$this->is_input_bom_included) { |
| 329 | $this->document->fseek(strlen($this->getInputBOM())); |
| 330 | } |
| 331 | echo $this->output_bom; |
| 332 | return strlen($this->output_bom) + (int) $this->document->fpassthru(); |
| 333 | } |
| 334 | /** |
| 335 | * Send the CSV headers. |
| 336 | * |
| 337 | * Adapted from Symfony\Component\HttpFoundation\ResponseHeaderBag::makeDisposition |
| 338 | * |
| 339 | * @throws Exception if the submitted header is invalid according to RFC 6266 |
| 340 | * |
| 341 | * @see https://tools.ietf.org/html/rfc6266#section-4.3 |
| 342 | */ |
| 343 | protected function sendHeaders(string $filename) : void |
| 344 | { |
| 345 | if (strlen($filename) != strcspn($filename, '\\/')) { |
| 346 | throw InvalidArgument::dueToInvalidHeaderFilename($filename); |
| 347 | } |
| 348 | $flag = FILTER_FLAG_STRIP_LOW; |
| 349 | if (strlen($filename) !== mb_strlen($filename)) { |
| 350 | $flag |= FILTER_FLAG_STRIP_HIGH; |
| 351 | } |
| 352 | /** @var string $filtered_name */ |
| 353 | $filtered_name = filter_var($filename, FILTER_UNSAFE_RAW, $flag); |
| 354 | $filename_fallback = str_replace('%', '', $filtered_name); |
| 355 | $disposition = sprintf('attachment; filename="%s"', str_replace('"', '\\"', $filename_fallback)); |
| 356 | if ($filename !== $filename_fallback) { |
| 357 | $disposition .= sprintf("; filename*=utf-8''%s", rawurlencode($filename)); |
| 358 | } |
| 359 | \header('Content-Type: text/csv'); |
| 360 | \header('Content-Transfer-Encoding: binary'); |
| 361 | \header('Content-Description: File Transfer'); |
| 362 | \header('Content-Disposition: ' . $disposition); |
| 363 | } |
| 364 | /** |
| 365 | * Sets the field delimiter. |
| 366 | * |
| 367 | * @throws InvalidArgument If the Csv control character is not one character only. |
| 368 | * |
| 369 | * @return static |
| 370 | */ |
| 371 | public function setDelimiter(string $delimiter) : self |
| 372 | { |
| 373 | if ($delimiter === $this->delimiter) { |
| 374 | return $this; |
| 375 | } |
| 376 | if (1 !== strlen($delimiter)) { |
| 377 | throw InvalidArgument::dueToInvalidDelimiterCharacter($delimiter, __METHOD__); |
| 378 | } |
| 379 | $this->delimiter = $delimiter; |
| 380 | $this->resetProperties(); |
| 381 | return $this; |
| 382 | } |
| 383 | /** |
| 384 | * Sets the field enclosure. |
| 385 | * |
| 386 | * @throws InvalidArgument If the Csv control character is not one character only. |
| 387 | * |
| 388 | * @return static |
| 389 | */ |
| 390 | public function setEnclosure(string $enclosure) : self |
| 391 | { |
| 392 | if ($enclosure === $this->enclosure) { |
| 393 | return $this; |
| 394 | } |
| 395 | if (1 !== strlen($enclosure)) { |
| 396 | throw InvalidArgument::dueToInvalidEnclosureCharacter($enclosure, __METHOD__); |
| 397 | } |
| 398 | $this->enclosure = $enclosure; |
| 399 | $this->resetProperties(); |
| 400 | return $this; |
| 401 | } |
| 402 | /** |
| 403 | * Sets the field escape character. |
| 404 | * |
| 405 | * @throws InvalidArgument If the Csv control character is not one character only. |
| 406 | * |
| 407 | * @return static |
| 408 | */ |
| 409 | public function setEscape(string $escape) : self |
| 410 | { |
| 411 | if ($escape === $this->escape) { |
| 412 | return $this; |
| 413 | } |
| 414 | if ('' !== $escape && 1 !== strlen($escape)) { |
| 415 | throw InvalidArgument::dueToInvalidEscapeCharacter($escape, __METHOD__); |
| 416 | } |
| 417 | $this->escape = $escape; |
| 418 | $this->resetProperties(); |
| 419 | return $this; |
| 420 | } |
| 421 | /** |
| 422 | * Enables BOM Stripping. |
| 423 | * |
| 424 | * @return static |
| 425 | */ |
| 426 | public function skipInputBOM() : self |
| 427 | { |
| 428 | $this->is_input_bom_included = \false; |
| 429 | return $this; |
| 430 | } |
| 431 | /** |
| 432 | * Disables skipping Input BOM. |
| 433 | * |
| 434 | * @return static |
| 435 | */ |
| 436 | public function includeInputBOM() : self |
| 437 | { |
| 438 | $this->is_input_bom_included = \true; |
| 439 | return $this; |
| 440 | } |
| 441 | /** |
| 442 | * Sets the BOM sequence to prepend the CSV on output. |
| 443 | * |
| 444 | * @return static |
| 445 | */ |
| 446 | public function setOutputBOM(string $str) : self |
| 447 | { |
| 448 | $this->output_bom = $str; |
| 449 | return $this; |
| 450 | } |
| 451 | /** |
| 452 | * append a stream filter. |
| 453 | * |
| 454 | * @param null|array $params |
| 455 | * |
| 456 | * @throws InvalidArgument If the stream filter API can not be appended |
| 457 | * @throws UnavailableFeature If the stream filter API can not be used |
| 458 | * |
| 459 | * @return static |
| 460 | */ |
| 461 | public function addStreamFilter(string $filtername, $params = null) : self |
| 462 | { |
| 463 | if (!$this->document instanceof Stream) { |
| 464 | throw UnavailableFeature::dueToUnsupportedStreamFilterApi(get_class($this->document)); |
| 465 | } |
| 466 | $this->document->appendFilter($filtername, static::STREAM_FILTER_MODE, $params); |
| 467 | $this->stream_filters[$filtername] = \true; |
| 468 | $this->resetProperties(); |
| 469 | $this->input_bom = null; |
| 470 | return $this; |
| 471 | } |
| 472 | } |
| 473 |