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