2222use League \Csv \Serializer \MappingFailed ;
2323use League \Csv \Serializer \TypeCastingFailed ;
2424use ReflectionException ;
25+ use SeekableIterator ;
2526use SplFileObject ;
2627
2728use function array_filter ;
2829use function array_reduce ;
2930use function array_unique ;
3031use function is_array ;
32+ use function is_string ;
3133use function iterator_count ;
3234use function strlen ;
3335use function substr ;
@@ -152,77 +154,60 @@ public function getHeader(): array
152154 */
153155 protected function setHeader (int $ offset ): array
154156 {
155- $ inputBom = null ;
156- $ header = $ this ->seekRow ($ offset );
157- if (0 === $ offset ) {
158- $ inputBom = Bom::tryFrom ($ this ->getInputBOM ());
159- $ header = $ this ->removeBOM (
160- $ header ,
161- !$ this ->is_input_bom_included ? $ inputBom ?->length() ?? 0 : 0 ,
162- $ this ->enclosure
163- );
157+ $ header = [];
158+ foreach ($ this ->getInnerIterator () as $ key => $ record ) {
159+ if ($ key === $ offset ) {
160+ $ header = $ record ;
161+ break ;
162+ }
164163 }
165164
166165 return match (true ) {
167166 [] === $ header ,
168167 [null ] === $ header ,
169168 [false ] === $ header ,
170- ['' ] === $ header && 0 === $ offset && null !== $ inputBom => throw SyntaxError::dueToHeaderNotFound ($ offset ),
169+ ['' ] === $ header && 0 === $ offset => throw SyntaxError::dueToHeaderNotFound ($ offset ),
171170 default => $ header ,
172171 };
173172 }
174173
175174 /**
176175 * @throws Exception
177176 *
178- * Returns the row at a given offset.
177+ * @return CallbackFilterIterator<int, array, SeekableIterator>
179178 */
180- protected function seekRow ( int $ offset ): array
179+ protected function getInnerIterator ( ): CallbackFilterIterator
181180 {
182- $ this ->getDocument ()->seek ($ offset );
183- $ record = $ this ->document ->current ();
184-
185- return match (true ) {
186- false === $ record => [],
187- default => (array ) $ record ,
188- };
181+ return new CallbackFilterIterator (
182+ $ this ->getSeekableIterator (),
183+ fn ($ record ): bool => is_array ($ record ) && ($ this ->is_empty_records_included || $ record !== [null ])
184+ );
189185 }
190186
191187 /**
192188 * @throws Exception
193189 *
194- * Returns the document as an Iterator.
190+ * @return SeekableIterator<int, array>
195191 */
196- protected function getDocument (): SplFileObject | Stream
192+ protected function getSeekableIterator (): SeekableIterator
197193 {
198- $ this ->document ->setFlags (SplFileObject::READ_CSV | SplFileObject::READ_AHEAD );
199- $ this ->document ->setCsvControl ($ this ->delimiter , $ this ->enclosure , $ this ->escape );
200- $ this ->document ->rewind ();
194+ $ document = $ this ->getDocument ();
201195
202- return $ this ->document ;
196+ return $ this ->is_input_bom_included ? $ document : new SkipBomIterator ( $ document ) ;
203197 }
204198
205199 /**
206- * Strips the BOM sequence from a record.
207- *
208- * @param array<string> $record
200+ * @throws Exception
209201 *
210- * @return array<string>
202+ * Returns the document as an Iterator.
211203 */
212- protected function removeBOM ( array $ record , int $ bom_length , string $ enclosure ): array
204+ protected function getDocument ( ): SplFileObject | Stream
213205 {
214- if ([] === $ record || !is_string ($ record [0 ]) || 0 === $ bom_length || strlen ($ record [0 ]) < $ bom_length ) {
215- return $ record ;
216- }
217-
218- $ record [0 ] = substr ($ record [0 ], $ bom_length );
219- if ($ enclosure .$ enclosure !== substr ($ record [0 ].$ record [0 ], strlen ($ record [0 ]) - 1 , 2 )) {
220- return $ record ;
221- }
222-
223- $ record [0 ] = substr ($ record [0 ], 1 , -1 );
206+ $ this ->document ->setFlags (SplFileObject::READ_CSV | SplFileObject::READ_AHEAD );
207+ $ this ->document ->setCsvControl ($ this ->delimiter , $ this ->enclosure , $ this ->escape );
208+ $ this ->document ->rewind ();
224209
225- return $ record ;
210+ return $ this -> document ;
226211 }
227212
228213 public function fetchColumn (string |int $ index = 0 ): Iterator
@@ -245,18 +230,17 @@ public function first(): array
245230
246231 protected function getLastRecord (array $ header ): array
247232 {
248- $ this ->document ->setFlags (SplFileObject::READ_CSV );
249- $ this ->document ->setCsvControl ($ this ->delimiter , $ this ->enclosure , $ this ->escape );
250- $ this ->document ->seek (PHP_INT_MAX );
251- $ offset = $ this ->document ->key ();
233+ $ document = $ this ->getSeekableIterator ();
234+ $ document ->seek (PHP_INT_MAX );
235+ $ offset = $ document ->key ();
252236 $ row = false ;
253237 for (; $ offset >= 0 ; --$ offset ) {
254238 if ($ this ->header_offset === $ offset ) {
255239 continue ;
256240 }
257- $ this -> document ->seek ($ offset );
241+ $ document ->seek ($ offset );
258242 /** @var array|false $row */
259- $ row = $ this -> document ->current ();
243+ $ row = $ document ->current ();
260244 if ($ row !== [null ] && false !== $ row ) {
261245 break ;
262246 }
@@ -266,10 +250,6 @@ protected function getLastRecord(array $header): array
266250 return [];
267251 }
268252
269- if (0 === $ offset ) {
270- $ row = $ this ->removeBOM ($ row , $ this ->input_bom ?->length() ?? 0 , $ this ->enclosure );
271- }
272-
273253 $ formatter = fn (array $ record ): array => array_reduce (
274254 $ this ->formatters ,
275255 fn (array $ record , Closure $ formatter ): array => $ formatter ($ record ),
@@ -279,7 +259,6 @@ protected function getLastRecord(array $header): array
279259 $ record = $ row ;
280260 if ([] === $ header ) {
281261 $ header = $ this ->getHeader ();
282- ;
283262 }
284263
285264 if ([] !== $ header ) {
@@ -533,10 +512,7 @@ public function selectAllExcept(string|int ...$columns): TabularDataReader
533512 */
534513 public function getRecords (array $ header = []): Iterator
535514 {
536- return $ this ->combineHeader (
537- $ this ->prepareRecords (),
538- $ this ->prepareHeader ($ header )
539- );
515+ return $ this ->combineHeader ($ this ->prepareRecords (), $ this ->prepareHeader ($ header ));
540516 }
541517
542518 /**
@@ -567,13 +543,7 @@ public function getRecordsAsObject(string $className, array $header = []): Itera
567543 */
568544 protected function prepareRecords (): Iterator
569545 {
570- $ normalized = fn ($ record ): bool => is_array ($ record ) && ($ this ->is_empty_records_included || $ record !== [null ]);
571- $ bom = null ;
572- if (!$ this ->is_input_bom_included ) {
573- $ bom = Bom::tryFrom ($ this ->getInputBOM ());
574- }
575-
576- $ records = $ this ->stripBOM (new CallbackFilterIterator ($ this ->getDocument (), $ normalized ), $ bom );
546+ $ records = $ this ->getInnerIterator ();
577547 if (null !== $ this ->header_offset ) {
578548 $ records = new CallbackFilterIterator ($ records , fn (array $ record , int $ offset ): bool => $ offset !== $ this ->header_offset );
579549 }
@@ -585,35 +555,6 @@ protected function prepareRecords(): Iterator
585555 return $ records ;
586556 }
587557
588- /**
589- * Strips the BOM sequence from the returned records if necessary.
590- */
591- protected function stripBOM (Iterator $ iterator , ?Bom $ bom ): Iterator
592- {
593- if (null === $ bom ) {
594- return $ iterator ;
595- }
596-
597- $ bomLength = $ bom ->length ();
598- $ mapper = function (array $ record , int $ index ) use ($ bomLength ): array {
599- if (0 !== $ index ) {
600- return $ record ;
601- }
602-
603- $ record = $ this ->removeBOM ($ record , $ bomLength , $ this ->enclosure );
604-
605- return match ($ record ) {
606- ['' ] => [null ],
607- default => $ record ,
608- };
609- };
610-
611- return new CallbackFilterIterator (
612- new MapIterator ($ iterator , $ mapper ),
613- fn (array $ record ): bool => $ this ->is_empty_records_included || $ record !== [null ]
614- );
615- }
616-
617558 /**
618559 * @param array<string> $header
619560 *
@@ -671,6 +612,90 @@ protected function combineHeader(Iterator $iterator, array $header): Iterator
671612 };
672613 }
673614
615+ /**
616+ * DEPRECATION WARNING! This method will be removed in the next major point release.
617+ *
618+ * Returns the row at a given offset.
619+ *
620+ * @deprecated
621+ * @throws Exception
622+ *
623+ * @deprecated since version 9.29 without any replacement
624+ * @codeCoverageIgnore
625+ */
626+ protected function seekRow (int $ offset ): array
627+ {
628+ $ this ->getDocument ()->seek ($ offset );
629+ $ record = $ this ->document ->current ();
630+
631+ return match (true ) {
632+ false === $ record => [],
633+ default => (array ) $ record ,
634+ };
635+ }
636+
637+ /**
638+ * Strips the BOM sequence from a record.
639+ *
640+ * DEPRECATION WARNING! This method will be removed in the next major point release.
641+ *
642+ * @param array<string> $record
643+ *
644+ * @return array<string>
645+ *
646+ * @deprecated since version 9.29 without any replacement
647+ * @codeCoverageIgnore
648+ */
649+ protected function removeBOM (array $ record , int $ bom_length , string $ enclosure ): array
650+ {
651+ if ([] === $ record || !is_string ($ record [0 ]) || 0 === $ bom_length || strlen ($ record [0 ]) < $ bom_length ) {
652+ return $ record ;
653+ }
654+
655+ $ record [0 ] = substr ($ record [0 ], $ bom_length );
656+ if ($ enclosure .$ enclosure !== substr ($ record [0 ].$ record [0 ], strlen ($ record [0 ]) - 1 , 2 )) {
657+ return $ record ;
658+ }
659+
660+ $ record [0 ] = substr ($ record [0 ], 1 , -1 );
661+
662+ return $ record ;
663+ }
664+
665+ /**
666+ * Strips the BOM sequence from the returned records if necessary.
667+ *
668+ * DEPRECATION WARNING! This method will be removed in the next major point release.
669+ *
670+ * @deprecated since version 9.29 without any replacement
671+ * @codeCoverageIgnore
672+ */
673+ protected function stripBOM (Iterator $ iterator , ?Bom $ bom ): Iterator
674+ {
675+ if (null === $ bom ) {
676+ return $ iterator ;
677+ }
678+
679+ $ bomLength = $ bom ->length ();
680+ $ mapper = function (array $ record , int $ index ) use ($ bomLength ): array {
681+ if (0 !== $ index ) {
682+ return $ record ;
683+ }
684+
685+ $ record = $ this ->removeBOM ($ record , $ bomLength , $ this ->enclosure );
686+
687+ return match ($ record ) {
688+ ['' ] => [null ],
689+ default => $ record ,
690+ };
691+ };
692+
693+ return new CallbackFilterIterator (
694+ new MapIterator ($ iterator , $ mapper ),
695+ fn (array $ record ): bool => $ this ->is_empty_records_included || $ record !== [null ]
696+ );
697+ }
698+
674699 /**
675700 * DEPRECATION WARNING! This method will be removed in the next major point release.
676701 *
0 commit comments