12
12
namespace Symfony \Component \Serializer \Encoder ;
13
13
14
14
use Symfony \Component \Serializer \Exception \InvalidArgumentException ;
15
+ use Symfony \Component \Serializer \Exception \UnexpectedValueException ;
15
16
16
17
/**
17
18
* Encodes CSV data.
@@ -30,6 +31,9 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
30
31
const ESCAPE_FORMULAS_KEY = 'csv_escape_formulas ' ;
31
32
const AS_COLLECTION_KEY = 'as_collection ' ;
32
33
const NO_HEADERS_KEY = 'no_headers ' ;
34
+ const OUTPUT_UTF8_BOM_KEY = 'output_utf8_bom ' ;
35
+
36
+ private const UTF8_BOM = "\xEF\xBB\xBF" ;
33
37
34
38
private $ formulasStartCharacters = ['= ' , '- ' , '+ ' , '@ ' ];
35
39
private $ defaultContext = [
@@ -40,6 +44,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
40
44
self ::HEADERS_KEY => [],
41
45
self ::KEY_SEPARATOR_KEY => '. ' ,
42
46
self ::NO_HEADERS_KEY => false ,
47
+ self ::OUTPUT_UTF8_BOM_KEY => false ,
43
48
];
44
49
45
50
/**
@@ -90,7 +95,7 @@ public function encode($data, $format, array $context = [])
90
95
}
91
96
}
92
97
93
- list ($ delimiter , $ enclosure , $ escapeChar , $ keySeparator , $ headers , $ escapeFormulas ) = $ this ->getCsvOptions ($ context );
98
+ list ($ delimiter , $ enclosure , $ escapeChar , $ keySeparator , $ headers , $ escapeFormulas, $ outputBom ) = $ this ->getCsvOptions ($ context );
94
99
95
100
foreach ($ data as &$ value ) {
96
101
$ flattened = [];
@@ -114,6 +119,14 @@ public function encode($data, $format, array $context = [])
114
119
$ value = stream_get_contents ($ handle );
115
120
fclose ($ handle );
116
121
122
+ if ($ outputBom ) {
123
+ if (!preg_match ('//u ' , $ value )) {
124
+ throw new UnexpectedValueException ('You are trying to add an UTF-8 BOM to a non UTF-8 text. ' );
125
+ }
126
+
127
+ $ value = self ::UTF8_BOM .$ value ;
128
+ }
129
+
117
130
return $ value ;
118
131
}
119
132
@@ -134,6 +147,10 @@ public function decode($data, $format, array $context = [])
134
147
fwrite ($ handle , $ data );
135
148
rewind ($ handle );
136
149
150
+ if (0 === strpos ($ data , self ::UTF8_BOM )) {
151
+ fseek ($ handle , \strlen (self ::UTF8_BOM ));
152
+ }
153
+
137
154
$ headers = null ;
138
155
$ nbHeaders = 0 ;
139
156
$ headerCount = [];
@@ -238,12 +255,13 @@ private function getCsvOptions(array $context): array
238
255
$ keySeparator = $ context [self ::KEY_SEPARATOR_KEY ] ?? $ this ->defaultContext [self ::KEY_SEPARATOR_KEY ];
239
256
$ headers = $ context [self ::HEADERS_KEY ] ?? $ this ->defaultContext [self ::HEADERS_KEY ];
240
257
$ escapeFormulas = $ context [self ::ESCAPE_FORMULAS_KEY ] ?? $ this ->defaultContext [self ::ESCAPE_FORMULAS_KEY ];
258
+ $ outputBom = $ context [self ::OUTPUT_UTF8_BOM_KEY ] ?? $ this ->defaultContext [self ::OUTPUT_UTF8_BOM_KEY ];
241
259
242
260
if (!\is_array ($ headers )) {
243
261
throw new InvalidArgumentException (sprintf ('The "%s" context variable must be an array or null, given "%s". ' , self ::HEADERS_KEY , \gettype ($ headers )));
244
262
}
245
263
246
- return [$ delimiter , $ enclosure , $ escapeChar , $ keySeparator , $ headers , $ escapeFormulas ];
264
+ return [$ delimiter , $ enclosure , $ escapeChar , $ keySeparator , $ headers , $ escapeFormulas, $ outputBom ];
247
265
}
248
266
249
267
/**
0 commit comments