| 1 |
<?php |
| 2 |
|
| 3 |
namespace OpenSpout\Common\Helper; |
| 4 |
|
| 5 |
use OpenSpout\Common\Exception\EncodingConversionException; |
| 6 |
|
| 7 |
/** |
| 8 |
* This class provides helper functions to work with encodings. |
| 9 |
*/ |
| 10 |
class EncodingHelper |
| 11 |
{ |
| 12 |
/** Definition of the encodings that can have a BOM */ |
| 13 |
public const ENCODING_UTF8 = 'UTF-8'; |
| 14 |
public const ENCODING_UTF16_LE = 'UTF-16LE'; |
| 15 |
public const ENCODING_UTF16_BE = 'UTF-16BE'; |
| 16 |
public const ENCODING_UTF32_LE = 'UTF-32LE'; |
| 17 |
public const ENCODING_UTF32_BE = 'UTF-32BE'; |
| 18 |
|
| 19 |
/** Definition of the BOMs for the different encodings */ |
| 20 |
public const BOM_UTF8 = "\xEF\xBB\xBF"; |
| 21 |
public const BOM_UTF16_LE = "\xFF\xFE"; |
| 22 |
public const BOM_UTF16_BE = "\xFE\xFF"; |
| 23 |
public const BOM_UTF32_LE = "\xFF\xFE\x00\x00"; |
| 24 |
public const BOM_UTF32_BE = "\x00\x00\xFE\xFF"; |
| 25 |
|
| 26 |
/** @var \OpenSpout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */ |
| 27 |
protected $globalFunctionsHelper; |
| 28 |
|
| 29 |
/** @var array Map representing the encodings supporting BOMs (key) and their associated BOM (value) */ |
| 30 |
protected $supportedEncodingsWithBom; |
| 31 |
|
| 32 |
/** |
| 33 |
* @param \OpenSpout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper |
| 34 |
*/ |
| 35 |
public function __construct($globalFunctionsHelper) |
| 36 |
{ |
| 37 |
$this->globalFunctionsHelper = $globalFunctionsHelper; |
| 38 |
|
| 39 |
$this->supportedEncodingsWithBom = [ |
| 40 |
self::ENCODING_UTF8 => self::BOM_UTF8, |
| 41 |
self::ENCODING_UTF16_LE => self::BOM_UTF16_LE, |
| 42 |
self::ENCODING_UTF16_BE => self::BOM_UTF16_BE, |
| 43 |
self::ENCODING_UTF32_LE => self::BOM_UTF32_LE, |
| 44 |
self::ENCODING_UTF32_BE => self::BOM_UTF32_BE, |
| 45 |
]; |
| 46 |
} |
| 47 |
|
| 48 |
/** |
| 49 |
* Returns the number of bytes to use as offset in order to skip the BOM. |
| 50 |
* |
| 51 |
* @param resource $filePointer Pointer to the file to check |
| 52 |
* @param string $encoding Encoding of the file to check |
| 53 |
* |
| 54 |
* @return int Bytes offset to apply to skip the BOM (0 means no BOM) |
| 55 |
*/ |
| 56 |
public function getBytesOffsetToSkipBOM($filePointer, $encoding) |
| 57 |
{ |
| 58 |
$byteOffsetToSkipBom = 0; |
| 59 |
|
| 60 |
if ($this->hasBOM($filePointer, $encoding)) { |
| 61 |
$bomUsed = $this->supportedEncodingsWithBom[$encoding]; |
| 62 |
|
| 63 |
// we skip the N first bytes |
| 64 |
$byteOffsetToSkipBom = \strlen($bomUsed); |
| 65 |
} |
| 66 |
|
| 67 |
return $byteOffsetToSkipBom; |
| 68 |
} |
| 69 |
|
| 70 |
/** |
| 71 |
* Attempts to convert a non UTF-8 string into UTF-8. |
| 72 |
* |
| 73 |
* @param string $string Non UTF-8 string to be converted |
| 74 |
* @param string $sourceEncoding The encoding used to encode the source string |
| 75 |
* |
| 76 |
* @throws \OpenSpout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed |
| 77 |
* |
| 78 |
* @return string The converted, UTF-8 string |
| 79 |
*/ |
| 80 |
public function attemptConversionToUTF8($string, $sourceEncoding) |
| 81 |
{ |
| 82 |
return $this->attemptConversion($string, $sourceEncoding, self::ENCODING_UTF8); |
| 83 |
} |
| 84 |
|
| 85 |
/** |
| 86 |
* Attempts to convert a UTF-8 string into the given encoding. |
| 87 |
* |
| 88 |
* @param string $string UTF-8 string to be converted |
| 89 |
* @param string $targetEncoding The encoding the string should be re-encoded into |
| 90 |
* |
| 91 |
* @throws \OpenSpout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed |
| 92 |
* |
| 93 |
* @return string The converted string, encoded with the given encoding |
| 94 |
*/ |
| 95 |
public function attemptConversionFromUTF8($string, $targetEncoding) |
| 96 |
{ |
| 97 |
return $this->attemptConversion($string, self::ENCODING_UTF8, $targetEncoding); |
| 98 |
} |
| 99 |
|
| 100 |
/** |
| 101 |
* Returns whether the file identified by the given pointer has a BOM. |
| 102 |
* |
| 103 |
* @param resource $filePointer Pointer to the file to check |
| 104 |
* @param string $encoding Encoding of the file to check |
| 105 |
* |
| 106 |
* @return bool TRUE if the file has a BOM, FALSE otherwise |
| 107 |
*/ |
| 108 |
protected function hasBOM($filePointer, $encoding) |
| 109 |
{ |
| 110 |
$hasBOM = false; |
| 111 |
|
| 112 |
$this->globalFunctionsHelper->rewind($filePointer); |
| 113 |
|
| 114 |
if (\array_key_exists($encoding, $this->supportedEncodingsWithBom)) { |
| 115 |
$potentialBom = $this->supportedEncodingsWithBom[$encoding]; |
| 116 |
$numBytesInBom = \strlen($potentialBom); |
| 117 |
|
| 118 |
$hasBOM = ($this->globalFunctionsHelper->fgets($filePointer, $numBytesInBom + 1) === $potentialBom); |
| 119 |
} |
| 120 |
|
| 121 |
return $hasBOM; |
| 122 |
} |
| 123 |
|
| 124 |
/** |
| 125 |
* Attempts to convert the given string to the given encoding. |
| 126 |
* Depending on what is installed on the server, we will try to iconv or mbstring. |
| 127 |
* |
| 128 |
* @param string $string string to be converted |
| 129 |
* @param string $sourceEncoding The encoding used to encode the source string |
| 130 |
* @param string $targetEncoding The encoding the string should be re-encoded into |
| 131 |
* |
| 132 |
* @throws \OpenSpout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed |
| 133 |
* |
| 134 |
* @return string The converted string, encoded with the given encoding |
| 135 |
*/ |
| 136 |
protected function attemptConversion($string, $sourceEncoding, $targetEncoding) |
| 137 |
{ |
| 138 |
// if source and target encodings are the same, it's a no-op |
| 139 |
if ($sourceEncoding === $targetEncoding) { |
| 140 |
return $string; |
| 141 |
} |
| 142 |
|
| 143 |
$convertedString = null; |
| 144 |
|
| 145 |
if ($this->canUseIconv()) { |
| 146 |
$convertedString = $this->globalFunctionsHelper->iconv($string, $sourceEncoding, $targetEncoding); |
| 147 |
} elseif ($this->canUseMbString()) { |
| 148 |
$convertedString = $this->globalFunctionsHelper->mb_convert_encoding($string, $sourceEncoding, $targetEncoding); |
| 149 |
} else { |
| 150 |
throw new EncodingConversionException("The conversion from {$sourceEncoding} to {$targetEncoding} is not supported. Please install \"iconv\" or \"PHP Intl\"."); |
| 151 |
} |
| 152 |
|
| 153 |
if (false === $convertedString) { |
| 154 |
throw new EncodingConversionException("The conversion from {$sourceEncoding} to {$targetEncoding} failed."); |
| 155 |
} |
| 156 |
|
| 157 |
return $convertedString; |
| 158 |
} |
| 159 |
|
| 160 |
/** |
| 161 |
* Returns whether "iconv" can be used. |
| 162 |
* |
| 163 |
* @return bool TRUE if "iconv" is available and can be used, FALSE otherwise |
| 164 |
*/ |
| 165 |
protected function canUseIconv() |
| 166 |
{ |
| 167 |
return $this->globalFunctionsHelper->function_exists('iconv'); |
| 168 |
} |
| 169 |
|
| 170 |
/** |
| 171 |
* Returns whether "mb_string" functions can be used. |
| 172 |
* These functions come with the PHP Intl package. |
| 173 |
* |
| 174 |
* @return bool TRUE if "mb_string" functions are available and can be used, FALSE otherwise |
| 175 |
*/ |
| 176 |
protected function canUseMbString() |
| 177 |
{ |
| 178 |
return $this->globalFunctionsHelper->function_exists('mb_convert_encoding'); |
| 179 |
} |
| 180 |
} |
| 181 |
|