1313class Decoder extends EventEmitter implements ReadableStreamInterface
1414{
1515 private $ input ;
16- private $ temp = false ;
1716
1817 private $ delimiter ;
1918 private $ enclosure ;
2019 private $ escapeChar ;
2120 private $ maxlength ;
2221
2322 private $ buffer = '' ;
23+ private $ offset = 0 ;
2424 private $ closed = false ;
2525
2626 public function __construct (ReadableStreamInterface $ input , $ delimiter = ', ' , $ enclosure = '" ' , $ escapeChar = '\\' , $ maxlength = 65536 )
@@ -35,8 +35,6 @@ public function __construct(ReadableStreamInterface $input, $delimiter = ',', $e
3535 return $ this ->close ();
3636 }
3737
38- $ this ->temp = fopen ('php://memory ' , 'r+ ' );
39-
4038 $ this ->input ->on ('data ' , array ($ this , 'handleData ' ));
4139 $ this ->input ->on ('end ' , array ($ this , 'handleEnd ' ));
4240 $ this ->input ->on ('error ' , array ($ this , 'handleError ' ));
@@ -56,12 +54,6 @@ public function close()
5654
5755 $ this ->closed = true ;
5856 $ this ->buffer = '' ;
59-
60- if ($ this ->temp !== false ) {
61- fclose ($ this ->temp );
62- $ this ->temp = false ;
63- }
64-
6557 $ this ->input ->close ();
6658
6759 $ this ->emit ('close ' );
@@ -91,21 +83,34 @@ public function handleData($data)
9183 $ this ->buffer .= $ data ;
9284
9385 // keep parsing while a newline has been found
94- while (($ newline = strpos ($ this ->buffer , "\n" )) !== false && $ newline <= $ this ->maxlength ) {
95- // read data up until newline and remove from buffer
96- ftruncate ( $ this -> temp , 0 );
97- fwrite ( $ this -> temp , ( string ) substr ($ this ->buffer , 0 , $ newline));
98- rewind ( $ this ->temp );
99- $ this -> buffer = ( string ) substr ( $ this ->buffer , $ newline + 1 );
100-
101- $ data = fgetcsv ( $ this -> temp , 0 , $ this -> delimiter , $ this -> enclosure , $ this -> escapeChar );
102-
103- // abort stream if decoding failed
104- if ($ data === false ) {
86+ while (($ newline = \ strpos ($ this ->buffer , "\n" , $ this -> offset )) !== false && $ newline <= $ this ->maxlength ) {
87+ // read data up until newline and try to parse
88+ $ data = \str_getcsv (
89+ \ substr ($ this ->buffer , 0 , $ newline + 1 ),
90+ $ this ->delimiter ,
91+ $ this ->enclosure ,
92+ $ this -> escapeChar
93+ );
94+
95+ // unable to decode? abort
96+ if ($ data === false || \end ( $ data ) === null ) {
10597 $ this ->handleError (new \RuntimeException ('Unable to decode CSV ' ));
10698 return ;
10799 }
108100
101+ // the last parsed cell value ends with a newline and the buffer does not end with end quote?
102+ // this looks like a multiline value, so only remember offset and wait for next newline
103+ $ last = \substr (\end ($ data ), -1 );
104+ \reset ($ data );
105+ if ($ last === "\n" && ($ newline === 1 || $ this ->buffer [$ newline - 1 ] !== $ this ->enclosure )) {
106+ $ this ->offset = $ newline + 1 ;
107+ continue ;
108+ }
109+
110+ // parsing successful => remove from buffer and emit
111+ $ this ->buffer = (string )\substr ($ this ->buffer , $ newline + 1 );
112+ $ this ->offset = 0 ;
113+
109114 $ this ->emit ('data ' , array ($ data ));
110115 }
111116
@@ -121,6 +126,10 @@ public function handleEnd()
121126 $ this ->handleData ("\n" );
122127 }
123128
129+ if ($ this ->buffer !== '' ) {
130+ $ this ->handleError (new \RuntimeException ('Unable to decode CSV ' ));
131+ }
132+
124133 if (!$ this ->closed ) {
125134 $ this ->emit ('end ' );
126135 $ this ->close ();
0 commit comments