Failed Conditions
Pull Request — master (#3743)
by Adrien
14:08
created

StringHelper::returnString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Shared;
4
5
class StringHelper
6
{
7
    /**
8
     * Control characters array.
9
     *
10
     * @var string[]
11
     */
12
    private static $controlCharacters = [];
13
14
    /**
15
     * SYLK Characters array.
16
     *
17
     * @var array
18
     */
19
    private static $SYLKCharacters = [];
20
21
    /**
22
     * Decimal separator.
23
     *
24
     * @var ?string
25
     */
26
    private static $decimalSeparator;
27
28
    /**
29
     * Thousands separator.
30
     *
31
     * @var ?string
32
     */
33
    private static $thousandsSeparator;
34
35
    /**
36
     * Currency code.
37
     *
38
     * @var string
39
     */
40
    private static $currencyCode;
41
42
    /**
43
     * Is iconv extension avalable?
44
     *
45
     * @var ?bool
46
     */
47
    private static $isIconvEnabled;
48
49
    /**
50
     * iconv options.
51
     *
52
     * @var string
53
     */
54
    private static $iconvOptions = '//IGNORE//TRANSLIT';
55
56
    /**
57
     * Build control characters array.
58
     */
59 104
    private static function buildControlCharacters(): void
60
    {
61 104
        for ($i = 0; $i <= 31; ++$i) {
62 104
            if ($i != 9 && $i != 10 && $i != 13) {
63 104
                $find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_';
64 104
                $replace = chr($i);
65 104
                self::$controlCharacters[$find] = $replace;
66
            }
67
        }
68
    }
69
70
    /**
71
     * Build SYLK characters array.
72
     */
73 104
    private static function buildSYLKCharacters(): void
74
    {
75 104
        self::$SYLKCharacters = [
76 104
            "\x1B 0" => chr(0),
77 104
            "\x1B 1" => chr(1),
78 104
            "\x1B 2" => chr(2),
79 104
            "\x1B 3" => chr(3),
80 104
            "\x1B 4" => chr(4),
81 104
            "\x1B 5" => chr(5),
82 104
            "\x1B 6" => chr(6),
83 104
            "\x1B 7" => chr(7),
84 104
            "\x1B 8" => chr(8),
85 104
            "\x1B 9" => chr(9),
86 104
            "\x1B :" => chr(10),
87 104
            "\x1B ;" => chr(11),
88 104
            "\x1B <" => chr(12),
89 104
            "\x1B =" => chr(13),
90 104
            "\x1B >" => chr(14),
91 104
            "\x1B ?" => chr(15),
92 104
            "\x1B!0" => chr(16),
93 104
            "\x1B!1" => chr(17),
94 104
            "\x1B!2" => chr(18),
95 104
            "\x1B!3" => chr(19),
96 104
            "\x1B!4" => chr(20),
97 104
            "\x1B!5" => chr(21),
98 104
            "\x1B!6" => chr(22),
99 104
            "\x1B!7" => chr(23),
100 104
            "\x1B!8" => chr(24),
101 104
            "\x1B!9" => chr(25),
102 104
            "\x1B!:" => chr(26),
103 104
            "\x1B!;" => chr(27),
104 104
            "\x1B!<" => chr(28),
105 104
            "\x1B!=" => chr(29),
106 104
            "\x1B!>" => chr(30),
107 104
            "\x1B!?" => chr(31),
108 104
            "\x1B'?" => chr(127),
109 104
            "\x1B(0" => '€', // 128 in CP1252
110 104
            "\x1B(2" => '‚', // 130 in CP1252
111 104
            "\x1B(3" => 'ƒ', // 131 in CP1252
112 104
            "\x1B(4" => '„', // 132 in CP1252
113 104
            "\x1B(5" => '…', // 133 in CP1252
114 104
            "\x1B(6" => '†', // 134 in CP1252
115 104
            "\x1B(7" => '‡', // 135 in CP1252
116 104
            "\x1B(8" => 'ˆ', // 136 in CP1252
117 104
            "\x1B(9" => '‰', // 137 in CP1252
118 104
            "\x1B(:" => 'Š', // 138 in CP1252
119 104
            "\x1B(;" => '‹', // 139 in CP1252
120 104
            "\x1BNj" => 'Œ', // 140 in CP1252
121 104
            "\x1B(>" => 'Ž', // 142 in CP1252
122 104
            "\x1B)1" => '‘', // 145 in CP1252
123 104
            "\x1B)2" => '’', // 146 in CP1252
124 104
            "\x1B)3" => '“', // 147 in CP1252
125 104
            "\x1B)4" => '”', // 148 in CP1252
126 104
            "\x1B)5" => '•', // 149 in CP1252
127 104
            "\x1B)6" => '–', // 150 in CP1252
128 104
            "\x1B)7" => '—', // 151 in CP1252
129 104
            "\x1B)8" => '˜', // 152 in CP1252
130 104
            "\x1B)9" => '™', // 153 in CP1252
131 104
            "\x1B):" => 'š', // 154 in CP1252
132 104
            "\x1B);" => '›', // 155 in CP1252
133 104
            "\x1BNz" => 'œ', // 156 in CP1252
134 104
            "\x1B)>" => 'ž', // 158 in CP1252
135 104
            "\x1B)?" => 'Ÿ', // 159 in CP1252
136 104
            "\x1B*0" => ' ', // 160 in CP1252
137 104
            "\x1BN!" => '¡', // 161 in CP1252
138 104
            "\x1BN\"" => '¢', // 162 in CP1252
139 104
            "\x1BN#" => '£', // 163 in CP1252
140 104
            "\x1BN(" => '¤', // 164 in CP1252
141 104
            "\x1BN%" => '¥', // 165 in CP1252
142 104
            "\x1B*6" => '¦', // 166 in CP1252
143 104
            "\x1BN'" => '§', // 167 in CP1252
144 104
            "\x1BNH " => '¨', // 168 in CP1252
145 104
            "\x1BNS" => '©', // 169 in CP1252
146 104
            "\x1BNc" => 'ª', // 170 in CP1252
147 104
            "\x1BN+" => '«', // 171 in CP1252
148 104
            "\x1B*<" => '¬', // 172 in CP1252
149 104
            "\x1B*=" => '­', // 173 in CP1252
150 104
            "\x1BNR" => '®', // 174 in CP1252
151 104
            "\x1B*?" => '¯', // 175 in CP1252
152 104
            "\x1BN0" => '°', // 176 in CP1252
153 104
            "\x1BN1" => '±', // 177 in CP1252
154 104
            "\x1BN2" => '²', // 178 in CP1252
155 104
            "\x1BN3" => '³', // 179 in CP1252
156 104
            "\x1BNB " => '´', // 180 in CP1252
157 104
            "\x1BN5" => 'µ', // 181 in CP1252
158 104
            "\x1BN6" => '¶', // 182 in CP1252
159 104
            "\x1BN7" => '·', // 183 in CP1252
160 104
            "\x1B+8" => '¸', // 184 in CP1252
161 104
            "\x1BNQ" => '¹', // 185 in CP1252
162 104
            "\x1BNk" => 'º', // 186 in CP1252
163 104
            "\x1BN;" => '»', // 187 in CP1252
164 104
            "\x1BN<" => '¼', // 188 in CP1252
165 104
            "\x1BN=" => '½', // 189 in CP1252
166 104
            "\x1BN>" => '¾', // 190 in CP1252
167 104
            "\x1BN?" => '¿', // 191 in CP1252
168 104
            "\x1BNAA" => 'À', // 192 in CP1252
169 104
            "\x1BNBA" => 'Á', // 193 in CP1252
170 104
            "\x1BNCA" => 'Â', // 194 in CP1252
171 104
            "\x1BNDA" => 'Ã', // 195 in CP1252
172 104
            "\x1BNHA" => 'Ä', // 196 in CP1252
173 104
            "\x1BNJA" => 'Å', // 197 in CP1252
174 104
            "\x1BNa" => 'Æ', // 198 in CP1252
175 104
            "\x1BNKC" => 'Ç', // 199 in CP1252
176 104
            "\x1BNAE" => 'È', // 200 in CP1252
177 104
            "\x1BNBE" => 'É', // 201 in CP1252
178 104
            "\x1BNCE" => 'Ê', // 202 in CP1252
179 104
            "\x1BNHE" => 'Ë', // 203 in CP1252
180 104
            "\x1BNAI" => 'Ì', // 204 in CP1252
181 104
            "\x1BNBI" => 'Í', // 205 in CP1252
182 104
            "\x1BNCI" => 'Î', // 206 in CP1252
183 104
            "\x1BNHI" => 'Ï', // 207 in CP1252
184 104
            "\x1BNb" => 'Ð', // 208 in CP1252
185 104
            "\x1BNDN" => 'Ñ', // 209 in CP1252
186 104
            "\x1BNAO" => 'Ò', // 210 in CP1252
187 104
            "\x1BNBO" => 'Ó', // 211 in CP1252
188 104
            "\x1BNCO" => 'Ô', // 212 in CP1252
189 104
            "\x1BNDO" => 'Õ', // 213 in CP1252
190 104
            "\x1BNHO" => 'Ö', // 214 in CP1252
191 104
            "\x1B-7" => '×', // 215 in CP1252
192 104
            "\x1BNi" => 'Ø', // 216 in CP1252
193 104
            "\x1BNAU" => 'Ù', // 217 in CP1252
194 104
            "\x1BNBU" => 'Ú', // 218 in CP1252
195 104
            "\x1BNCU" => 'Û', // 219 in CP1252
196 104
            "\x1BNHU" => 'Ü', // 220 in CP1252
197 104
            "\x1B-=" => 'Ý', // 221 in CP1252
198 104
            "\x1BNl" => 'Þ', // 222 in CP1252
199 104
            "\x1BN{" => 'ß', // 223 in CP1252
200 104
            "\x1BNAa" => 'à', // 224 in CP1252
201 104
            "\x1BNBa" => 'á', // 225 in CP1252
202 104
            "\x1BNCa" => 'â', // 226 in CP1252
203 104
            "\x1BNDa" => 'ã', // 227 in CP1252
204 104
            "\x1BNHa" => 'ä', // 228 in CP1252
205 104
            "\x1BNJa" => 'å', // 229 in CP1252
206 104
            "\x1BNq" => 'æ', // 230 in CP1252
207 104
            "\x1BNKc" => 'ç', // 231 in CP1252
208 104
            "\x1BNAe" => 'è', // 232 in CP1252
209 104
            "\x1BNBe" => 'é', // 233 in CP1252
210 104
            "\x1BNCe" => 'ê', // 234 in CP1252
211 104
            "\x1BNHe" => 'ë', // 235 in CP1252
212 104
            "\x1BNAi" => 'ì', // 236 in CP1252
213 104
            "\x1BNBi" => 'í', // 237 in CP1252
214 104
            "\x1BNCi" => 'î', // 238 in CP1252
215 104
            "\x1BNHi" => 'ï', // 239 in CP1252
216 104
            "\x1BNs" => 'ð', // 240 in CP1252
217 104
            "\x1BNDn" => 'ñ', // 241 in CP1252
218 104
            "\x1BNAo" => 'ò', // 242 in CP1252
219 104
            "\x1BNBo" => 'ó', // 243 in CP1252
220 104
            "\x1BNCo" => 'ô', // 244 in CP1252
221 104
            "\x1BNDo" => 'õ', // 245 in CP1252
222 104
            "\x1BNHo" => 'ö', // 246 in CP1252
223 104
            "\x1B/7" => '÷', // 247 in CP1252
224 104
            "\x1BNy" => 'ø', // 248 in CP1252
225 104
            "\x1BNAu" => 'ù', // 249 in CP1252
226 104
            "\x1BNBu" => 'ú', // 250 in CP1252
227 104
            "\x1BNCu" => 'û', // 251 in CP1252
228 104
            "\x1BNHu" => 'ü', // 252 in CP1252
229 104
            "\x1B/=" => 'ý', // 253 in CP1252
230 104
            "\x1BN|" => 'þ', // 254 in CP1252
231 104
            "\x1BNHy" => 'ÿ', // 255 in CP1252
232 104
        ];
233
    }
234
235
    /**
236
     * Get whether iconv extension is available.
237
     *
238
     * @return bool
239
     */
240 194
    public static function getIsIconvEnabled()
241
    {
242 194
        if (isset(self::$isIconvEnabled)) {
243 194
            return self::$isIconvEnabled;
244
        }
245
246
        // Assume no problems with iconv
247 71
        self::$isIconvEnabled = true;
248
249
        // Fail if iconv doesn't exist
250 71
        if (!function_exists('iconv')) {
251
            self::$isIconvEnabled = false;
252 71
        } elseif (!@iconv('UTF-8', 'UTF-16LE', 'x')) {
253
            // Sometimes iconv is not working, and e.g. iconv('UTF-8', 'UTF-16LE', 'x') just returns false,
254
            self::$isIconvEnabled = false;
255 71
        } elseif (defined('PHP_OS') && @stristr(PHP_OS, 'AIX') && defined('ICONV_IMPL') && (@strcasecmp(ICONV_IMPL, 'unknown') == 0) && defined('ICONV_VERSION') && (@strcasecmp(ICONV_VERSION, 'unknown') == 0)) {
256
            // CUSTOM: IBM AIX iconv() does not work
257
            self::$isIconvEnabled = false;
258
        }
259
260
        // Deactivate iconv default options if they fail (as seen on IMB i)
261 71
        if (self::$isIconvEnabled && !@iconv('UTF-8', 'UTF-16LE' . self::$iconvOptions, 'x')) {
262
            self::$iconvOptions = '';
263
        }
264
265 71
        return self::$isIconvEnabled;
266
    }
267
268 556
    private static function buildCharacterSets(): void
269
    {
270 556
        if (empty(self::$controlCharacters)) {
271 104
            self::buildControlCharacters();
272
        }
273
274 556
        if (empty(self::$SYLKCharacters)) {
275 104
            self::buildSYLKCharacters();
276
        }
277
    }
278
279
    /**
280
     * Convert from OpenXML escaped control character to PHP control character.
281
     *
282
     * Excel 2007 team:
283
     * ----------------
284
     * That's correct, control characters are stored directly in the shared-strings table.
285
     * We do encode characters that cannot be represented in XML using the following escape sequence:
286
     * _xHHHH_ where H represents a hexadecimal character in the character's value...
287
     * So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
288
     * element or in the shared string <t> element.
289
     *
290
     * @param string $textValue Value to unescape
291
     */
292 437
    public static function controlCharacterOOXML2PHP($textValue): string
293
    {
294 437
        self::buildCharacterSets();
295
296 437
        return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $textValue);
297
    }
298
299
    /**
300
     * Convert from PHP control character to OpenXML escaped control character.
301
     *
302
     * Excel 2007 team:
303
     * ----------------
304
     * That's correct, control characters are stored directly in the shared-strings table.
305
     * We do encode characters that cannot be represented in XML using the following escape sequence:
306
     * _xHHHH_ where H represents a hexadecimal character in the character's value...
307
     * So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
308
     * element or in the shared string <t> element.
309
     *
310
     * @param string $textValue Value to escape
311
     */
312 228
    public static function controlCharacterPHP2OOXML($textValue): string
313
    {
314 228
        self::buildCharacterSets();
315
316 228
        return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $textValue);
317
    }
318
319
    /**
320
     * Try to sanitize UTF8, replacing invalid sequences with Unicode substitution characters.
321
     */
322 9043
    public static function sanitizeUTF8(string $textValue): string
323
    {
324 9043
        $textValue = str_replace(["\xef\xbf\xbe", "\xef\xbf\xbf"], "\xef\xbf\xbd", $textValue);
325 9043
        $subst = mb_substitute_character(); // default is question mark
326 9043
        mb_substitute_character(65533); // Unicode substitution character
327
        // Phpstan does not think this can return false.
328 9043
        $returnValue = mb_convert_encoding($textValue, 'UTF-8', 'UTF-8');
329 9043
        mb_substitute_character($subst);
330
331 9043
        return $returnValue;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $returnValue could return the type array which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
332
    }
333
334
    /**
335
     * Check if a string contains UTF8 data.
336
     */
337 9043
    public static function isUTF8(string $textValue): bool
338
    {
339 9043
        return $textValue === self::sanitizeUTF8($textValue);
340
    }
341
342
    /**
343
     * Formats a numeric value as a string for output in various output writers forcing
344
     * point as decimal separator in case locale is other than English.
345 1
     *
346
     * @param float|int|string $numericValue
347 1
     */
348
    public static function formatNumber($numericValue): string
349
    {
350
        if (is_float($numericValue)) {
351
            return str_replace(',', '.', (string) $numericValue);
352
        }
353
354
        return (string) $numericValue;
355
    }
356 807
357
    /**
358 807
     * Converts a UTF-8 string into BIFF8 Unicode string data (8-bit string length)
359 806
     * Writes the string using uncompressed notation, no rich text, no Asian phonetics
360
     * If mbstring extension is not available, ASCII is assumed, and compressed notation is used
361
     * although this will give wrong results for non-ASCII strings
362 256
     * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
363
     *
364
     * @param string $textValue UTF-8 encoded string
365
     * @param mixed[] $arrcRuns Details of rich text runs in $value
366
     */
367
    public static function UTF8toBIFF8UnicodeShort(string $textValue, array $arrcRuns = []): string
368
    {
369
        // character count
370
        $ln = self::countCharacters($textValue, 'UTF-8');
371
        // option flags
372
        if (empty($arrcRuns)) {
373
            $data = pack('CC', $ln, 0x0001);
374
            // characters
375 95
            $data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
376
        } else {
377
            $data = pack('vC', $ln, 0x09);
378 95
            $data .= pack('v', count($arrcRuns));
379
            // characters
380 95
            $data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
381 95
            foreach ($arrcRuns as $cRun) {
382
                $data .= pack('v', $cRun['strlen']);
383 95
                $data .= pack('v', $cRun['fontidx']);
384
            }
385 11
        }
386 11
387
        return $data;
388 11
    }
389 11
390 11
    /**
391 11
     * Converts a UTF-8 string into BIFF8 Unicode string data (16-bit string length)
392
     * Writes the string using uncompressed notation, no rich text, no Asian phonetics
393
     * If mbstring extension is not available, ASCII is assumed, and compressed notation is used
394
     * although this will give wrong results for non-ASCII strings
395 95
     * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
396
     *
397
     * @param string $textValue UTF-8 encoded string
398
     */
399
    public static function UTF8toBIFF8UnicodeLong(string $textValue): string
400
    {
401
        // characters
402
        $chars = self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
403
        $ln = (int) (strlen($chars) / 2);  // N.B. - strlen, not mb_strlen issue #642
404
405
        return pack('vC', $ln, 0x0001) . $chars;
406
    }
407 96
408
    /**
409
     * Convert string from one encoding to another.
410 96
     *
411 96
     * @param string $to Encoding to convert to, e.g. 'UTF-8'
412
     * @param string $from Encoding to convert from, e.g. 'UTF-16LE'
413 96
     */
414
    public static function convertEncoding(string $textValue, string $to, string $from): string
415
    {
416
        if (self::getIsIconvEnabled()) {
417
            $result = iconv($from, $to . self::$iconvOptions, $textValue);
418
            if (false !== $result) {
419
                return $result;
420
            }
421
        }
422 193
423
        return mb_convert_encoding($textValue, $to, $from);
1 ignored issue
show
Bug Best Practice introduced by
The expression return mb_convert_encoding($textValue, $to, $from) could return the type array which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
424 193
    }
425 193
426 193
    /**
427 193
     * Get character count.
428
     *
429
     * @param string $encoding Encoding
430
     *
431
     * @return int Character count
432
     */
433
    public static function countCharacters(string $textValue, string $encoding = 'UTF-8'): int
434
    {
435
        return mb_strlen($textValue, $encoding);
436
    }
437
438
    /**
439
     * Get character count using mb_strwidth rather than mb_strlen.
440
     *
441 9928
     * @param string $encoding Encoding
442
     *
443 9928
     * @return int Character count
444
     */
445
    public static function countCharactersDbcs(string $textValue, string $encoding = 'UTF-8'): int
446
    {
447
        return mb_strwidth($textValue, $encoding);
448
    }
449
450
    /**
451
     * Get a substring of a UTF-8 encoded string.
452
     *
453 68
     * @param string $textValue UTF-8 encoded string
454
     * @param int $offset Start offset
455 68
     * @param ?int $length Maximum number of characters in substring
456
     */
457
    public static function substring(string $textValue, int $offset, ?int $length = 0): string
458
    {
459
        return mb_substr($textValue, $offset, $length, 'UTF-8');
460
    }
461
462
    /**
463
     * Convert a UTF-8 encoded string to upper case.
464
     *
465 9899
     * @param string $textValue UTF-8 encoded string
466
     */
467 9899
    public static function strToUpper(string $textValue): string
468
    {
469
        return mb_convert_case($textValue, MB_CASE_UPPER, 'UTF-8');
470
    }
471
472
    /**
473
     * Convert a UTF-8 encoded string to lower case.
474
     *
475 9288
     * @param string $textValue UTF-8 encoded string
476
     */
477 9288
    public static function strToLower(string $textValue): string
478
    {
479
        return mb_convert_case($textValue, MB_CASE_LOWER, 'UTF-8');
480
    }
481
482
    /**
483
     * Convert a UTF-8 encoded string to title/proper case
484
     * (uppercase every first character in each word, lower case all other characters).
485 9522
     *
486
     * @param string $textValue UTF-8 encoded string
487 9522
     */
488
    public static function strToTitle(string $textValue): string
489
    {
490
        return mb_convert_case($textValue, MB_CASE_TITLE, 'UTF-8');
491
    }
492
493
    public static function mbIsUpper(string $character): bool
494
    {
495
        return mb_strtolower($character, 'UTF-8') !== $character;
496 19
    }
497
498 19
    /**
499
     * Splits a UTF-8 string into an array of individual characters.
500
     */
501 21
    public static function mbStrSplit(string $string): array
502
    {
503 21
        // Split at all position not after the start: ^
504
        // and not before the end: $
505
        $split = preg_split('/(?<!^)(?!$)/u', $string);
506
507
        return ($split === false) ? [] : $split;
508
    }
509 21
510
    /**
511
     * Reverse the case of a string, so that all uppercase characters become lowercase
512
     * and all lowercase characters become uppercase.
513 21
     *
514
     * @param string $textValue UTF-8 encoded string
515 21
     */
516
    public static function strCaseReverse(string $textValue): string
517
    {
518
        $characters = self::mbStrSplit($textValue);
519
        foreach ($characters as &$character) {
520
            if (self::mbIsUpper($character)) {
521
                $character = mb_strtolower($character, 'UTF-8');
522
            } else {
523
                $character = mb_strtoupper($character, 'UTF-8');
524 21
            }
525
        }
526 21
527 21
        return implode('', $characters);
528 21
    }
529 14
530
    /**
531 17
     * Get the decimal separator. If it has not yet been set explicitly, try to obtain number
532
     * formatting information from locale.
533
     */
534
    public static function getDecimalSeparator(): string
535 21
    {
536
        if (!isset(self::$decimalSeparator)) {
537
            $localeconv = localeconv();
538
            self::$decimalSeparator = ($localeconv['decimal_point'] != '')
539
                ? $localeconv['decimal_point'] : $localeconv['mon_decimal_point'];
540
541
            if (self::$decimalSeparator == '') {
542 1118
                // Default to .
543
                self::$decimalSeparator = '.';
544 1118
            }
545 36
        }
546 36
547 36
        return self::$decimalSeparator;
548
    }
549 36
550
    /**
551
     * Set the decimal separator. Only used by NumberFormat::toFormattedString()
552
     * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
553
     *
554
     * @param string $separator Character for decimal separator
555 1118
     */
556
    public static function setDecimalSeparator(string $separator): void
557
    {
558
        self::$decimalSeparator = $separator;
559
    }
560
561
    /**
562
     * Get the thousands separator. If it has not yet been set explicitly, try to obtain number
563
     * formatting information from locale.
564 885
     */
565
    public static function getThousandsSeparator(): string
566 885
    {
567
        if (!isset(self::$thousandsSeparator)) {
568
            $localeconv = localeconv();
569
            self::$thousandsSeparator = ($localeconv['thousands_sep'] != '')
570
                ? $localeconv['thousands_sep'] : $localeconv['mon_thousands_sep'];
571
572
            if (self::$thousandsSeparator == '') {
573 1118
                // Default to .
574
                self::$thousandsSeparator = ',';
575 1118
            }
576 36
        }
577 36
578 36
        return self::$thousandsSeparator;
579
    }
580 36
581
    /**
582
     * Set the thousands separator. Only used by NumberFormat::toFormattedString()
583
     * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
584
     *
585
     * @param string $separator Character for thousands separator
586 1118
     */
587
    public static function setThousandsSeparator(string $separator): void
588
    {
589
        self::$thousandsSeparator = $separator;
590
    }
591
592
    /**
593
     *    Get the currency code. If it has not yet been set explicitly, try to obtain the
594
     *        symbol information from locale.
595 885
     */
596
    public static function getCurrencyCode(): string
597 885
    {
598
        if (!empty(self::$currencyCode)) {
599
            return self::$currencyCode;
600
        }
601
        self::$currencyCode = '$';
602
        $localeconv = localeconv();
603
        if (!empty($localeconv['currency_symbol'])) {
604 912
            self::$currencyCode = $localeconv['currency_symbol'];
605
606 912
            return self::$currencyCode;
607 912
        }
608
        if (!empty($localeconv['int_curr_symbol'])) {
609 4
            self::$currencyCode = $localeconv['int_curr_symbol'];
610 4
611 4
            return self::$currencyCode;
612 4
        }
613
614 4
        return self::$currencyCode;
615
    }
616
617
    /**
618
     * Set the currency code. Only used by NumberFormat::toFormattedString()
619
     *        to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
620
     *
621
     * @param string $currencyCode Character for currency code
622
     */
623
    public static function setCurrencyCode(string $currencyCode): void
624
    {
625
        self::$currencyCode = $currencyCode;
626
    }
627
628
    /**
629
     * Convert SYLK encoded string to UTF-8.
630
     *
631 885
     * @param string $textValue SYLK encoded string
632
     *
633 885
     * @return string UTF-8 encoded string
634
     */
635
    public static function SYLKtoUTF8(string $textValue): string
636
    {
637
        self::buildCharacterSets();
638
639
        // If there is no escape character in the string there is nothing to do
640
        if (!str_contains($textValue, '')) {
641
            return $textValue;
642
        }
643 9
644
        foreach (self::$SYLKCharacters as $k => $v) {
645 9
            $textValue = str_replace($k, $v, $textValue);
646
        }
647
648 9
        return $textValue;
649 8
    }
650
651
    /**
652 2
     * Retrieve any leading numeric part of a string, or return the full string if no leading numeric
653 2
     * (handles basic integer or float, but not exponent or non decimal).
654
     *
655
     * @param string $textValue
656 2
     *
657
     * @return mixed string or only the leading numeric part of the string
658
     */
659
    public static function testStringAsNumeric($textValue)
660
    {
661
        if (is_numeric($textValue)) {
662
            return $textValue;
663
        }
664
        $v = (float) $textValue;
665
666
        return (is_numeric(substr($textValue, 0, strlen((string) $v)))) ? $v : $textValue;
667 278
    }
668
}
669