| 1 |
<?php |
| 2 |
/* |
| 3 |
Copyright (c) 2008 Sebastián Grignoli |
| 4 |
All rights reserved. |
| 5 |
|
| 6 |
Redistribution and use in source and binary forms, with or without |
| 7 |
modification, are permitted provided that the following conditions |
| 8 |
are met: |
| 9 |
1. Redistributions of source code must retain the above copyright |
| 10 |
notice, this list of conditions and the following disclaimer. |
| 11 |
2. Redistributions in binary form must reproduce the above copyright |
| 12 |
notice, this list of conditions and the following disclaimer in the |
| 13 |
documentation and/or other materials provided with the distribution. |
| 14 |
3. Neither the name of copyright holders nor the names of its |
| 15 |
contributors may be used to endorse or promote products derived |
| 16 |
from this software without specific prior written permission. |
| 17 |
|
| 18 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 |
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| 20 |
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 21 |
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS |
| 22 |
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 23 |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 24 |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 25 |
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 26 |
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 27 |
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 28 |
POSSIBILITY OF SUCH DAMAGE. |
| 29 |
*/ |
| 30 |
|
| 31 |
/** |
| 32 |
* @author "Sebastián Grignoli" <grignoli@gmail.com> |
| 33 |
* @package Encoding |
| 34 |
* @version 2.0 |
| 35 |
* @link https://github.com/neitanod/forceutf8 |
| 36 |
* @example https://github.com/neitanod/forceutf8 |
| 37 |
* @license Revised BSD |
| 38 |
*/ |
| 39 |
|
| 40 |
namespace ForceUTF8; |
| 41 |
|
| 42 |
class Encoding { |
| 43 |
|
| 44 |
const ICONV_TRANSLIT = "TRANSLIT"; |
| 45 |
const ICONV_IGNORE = "IGNORE"; |
| 46 |
const WITHOUT_ICONV = ""; |
| 47 |
|
| 48 |
protected static $win1252ToUtf8 = array( |
| 49 |
128 => "\xe2\x82\xac", |
| 50 |
|
| 51 |
130 => "\xe2\x80\x9a", |
| 52 |
131 => "\xc6\x92", |
| 53 |
132 => "\xe2\x80\x9e", |
| 54 |
133 => "\xe2\x80\xa6", |
| 55 |
134 => "\xe2\x80\xa0", |
| 56 |
135 => "\xe2\x80\xa1", |
| 57 |
136 => "\xcb\x86", |
| 58 |
137 => "\xe2\x80\xb0", |
| 59 |
138 => "\xc5\xa0", |
| 60 |
139 => "\xe2\x80\xb9", |
| 61 |
140 => "\xc5\x92", |
| 62 |
|
| 63 |
142 => "\xc5\xbd", |
| 64 |
|
| 65 |
|
| 66 |
145 => "\xe2\x80\x98", |
| 67 |
146 => "\xe2\x80\x99", |
| 68 |
147 => "\xe2\x80\x9c", |
| 69 |
148 => "\xe2\x80\x9d", |
| 70 |
149 => "\xe2\x80\xa2", |
| 71 |
150 => "\xe2\x80\x93", |
| 72 |
151 => "\xe2\x80\x94", |
| 73 |
152 => "\xcb\x9c", |
| 74 |
153 => "\xe2\x84\xa2", |
| 75 |
154 => "\xc5\xa1", |
| 76 |
155 => "\xe2\x80\xba", |
| 77 |
156 => "\xc5\x93", |
| 78 |
|
| 79 |
158 => "\xc5\xbe", |
| 80 |
159 => "\xc5\xb8" |
| 81 |
); |
| 82 |
|
| 83 |
protected static $brokenUtf8ToUtf8 = array( |
| 84 |
"\xc2\x80" => "\xe2\x82\xac", |
| 85 |
|
| 86 |
"\xc2\x82" => "\xe2\x80\x9a", |
| 87 |
"\xc2\x83" => "\xc6\x92", |
| 88 |
"\xc2\x84" => "\xe2\x80\x9e", |
| 89 |
"\xc2\x85" => "\xe2\x80\xa6", |
| 90 |
"\xc2\x86" => "\xe2\x80\xa0", |
| 91 |
"\xc2\x87" => "\xe2\x80\xa1", |
| 92 |
"\xc2\x88" => "\xcb\x86", |
| 93 |
"\xc2\x89" => "\xe2\x80\xb0", |
| 94 |
"\xc2\x8a" => "\xc5\xa0", |
| 95 |
"\xc2\x8b" => "\xe2\x80\xb9", |
| 96 |
"\xc2\x8c" => "\xc5\x92", |
| 97 |
|
| 98 |
"\xc2\x8e" => "\xc5\xbd", |
| 99 |
|
| 100 |
|
| 101 |
"\xc2\x91" => "\xe2\x80\x98", |
| 102 |
"\xc2\x92" => "\xe2\x80\x99", |
| 103 |
"\xc2\x93" => "\xe2\x80\x9c", |
| 104 |
"\xc2\x94" => "\xe2\x80\x9d", |
| 105 |
"\xc2\x95" => "\xe2\x80\xa2", |
| 106 |
"\xc2\x96" => "\xe2\x80\x93", |
| 107 |
"\xc2\x97" => "\xe2\x80\x94", |
| 108 |
"\xc2\x98" => "\xcb\x9c", |
| 109 |
"\xc2\x99" => "\xe2\x84\xa2", |
| 110 |
"\xc2\x9a" => "\xc5\xa1", |
| 111 |
"\xc2\x9b" => "\xe2\x80\xba", |
| 112 |
"\xc2\x9c" => "\xc5\x93", |
| 113 |
|
| 114 |
"\xc2\x9e" => "\xc5\xbe", |
| 115 |
"\xc2\x9f" => "\xc5\xb8" |
| 116 |
); |
| 117 |
|
| 118 |
protected static $utf8ToWin1252 = array( |
| 119 |
"\xe2\x82\xac" => "\x80", |
| 120 |
|
| 121 |
"\xe2\x80\x9a" => "\x82", |
| 122 |
"\xc6\x92" => "\x83", |
| 123 |
"\xe2\x80\x9e" => "\x84", |
| 124 |
"\xe2\x80\xa6" => "\x85", |
| 125 |
"\xe2\x80\xa0" => "\x86", |
| 126 |
"\xe2\x80\xa1" => "\x87", |
| 127 |
"\xcb\x86" => "\x88", |
| 128 |
"\xe2\x80\xb0" => "\x89", |
| 129 |
"\xc5\xa0" => "\x8a", |
| 130 |
"\xe2\x80\xb9" => "\x8b", |
| 131 |
"\xc5\x92" => "\x8c", |
| 132 |
|
| 133 |
"\xc5\xbd" => "\x8e", |
| 134 |
|
| 135 |
|
| 136 |
"\xe2\x80\x98" => "\x91", |
| 137 |
"\xe2\x80\x99" => "\x92", |
| 138 |
"\xe2\x80\x9c" => "\x93", |
| 139 |
"\xe2\x80\x9d" => "\x94", |
| 140 |
"\xe2\x80\xa2" => "\x95", |
| 141 |
"\xe2\x80\x93" => "\x96", |
| 142 |
"\xe2\x80\x94" => "\x97", |
| 143 |
"\xcb\x9c" => "\x98", |
| 144 |
"\xe2\x84\xa2" => "\x99", |
| 145 |
"\xc5\xa1" => "\x9a", |
| 146 |
"\xe2\x80\xba" => "\x9b", |
| 147 |
"\xc5\x93" => "\x9c", |
| 148 |
|
| 149 |
"\xc5\xbe" => "\x9e", |
| 150 |
"\xc5\xb8" => "\x9f" |
| 151 |
); |
| 152 |
|
| 153 |
static function toUTF8($text){ |
| 154 |
/** |
| 155 |
* Function \ForceUTF8\Encoding::toUTF8 |
| 156 |
* |
| 157 |
* This function leaves UTF8 characters alone, while converting almost all non-UTF8 to UTF8. |
| 158 |
* |
| 159 |
* It assumes that the encoding of the original string is either Windows-1252 or ISO 8859-1. |
| 160 |
* |
| 161 |
* It may fail to convert characters to UTF-8 if they fall into one of these scenarios: |
| 162 |
* |
| 163 |
* 1) when any of these characters: ÀÁÂÃÄ� |
| 164 |
ÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß |
| 165 |
* are followed by any of these: ("group B") |
| 166 |
* ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶•¸¹º»¼½¾¿ |
| 167 |
* For example: %ABREPRESENT%C9%BB. «REPRESENTÉ» |
| 168 |
* The "«" (%AB) character will be converted, but the "É" followed by "»" (%C9%BB) |
| 169 |
* is also a valid unicode character, and will be left unchanged. |
| 170 |
* |
| 171 |
* 2) when any of these: àáâãäåæçèéêëìíîï are followed by TWO chars from group B, |
| 172 |
* 3) when any of these: ðñòó are followed by THREE chars from group B. |
| 173 |
* |
| 174 |
* @name toUTF8 |
| 175 |
* @param string $text Any string. |
| 176 |
* @return string The same string, UTF8 encoded |
| 177 |
* |
| 178 |
*/ |
| 179 |
|
| 180 |
if(is_array($text)) |
| 181 |
{ |
| 182 |
foreach($text as $k => $v) |
| 183 |
{ |
| 184 |
$text[$k] = self::toUTF8($v); |
| 185 |
} |
| 186 |
return $text; |
| 187 |
} |
| 188 |
|
| 189 |
if(!is_string($text)) { |
| 190 |
return $text; |
| 191 |
} |
| 192 |
|
| 193 |
$max = self::strlen($text); |
| 194 |
|
| 195 |
$buf = ""; |
| 196 |
for($i = 0; $i < $max; $i++){ |
| 197 |
$c1 = $text[$i]; |
| 198 |
if($c1>="\xc0"){ //Should be converted to UTF8, if it's not UTF8 already |
| 199 |
$c2 = $i+1 >= $max? "\x00" : $text[$i+1]; |
| 200 |
$c3 = $i+2 >= $max? "\x00" : $text[$i+2]; |
| 201 |
$c4 = $i+3 >= $max? "\x00" : $text[$i+3]; |
| 202 |
if($c1 >= "\xc0" & $c1 <= "\xdf"){ //looks like 2 bytes UTF8 |
| 203 |
if($c2 >= "\x80" && $c2 <= "\xbf"){ //yeah, almost sure it's UTF8 already |
| 204 |
$buf .= $c1 . $c2; |
| 205 |
$i++; |
| 206 |
} else { //not valid UTF8. Convert it. |
| 207 |
$cc1 = (chr(ord($c1) / 64) | "\xc0"); |
| 208 |
$cc2 = ($c1 & "\x3f") | "\x80"; |
| 209 |
$buf .= $cc1 . $cc2; |
| 210 |
} |
| 211 |
} elseif($c1 >= "\xe0" & $c1 <= "\xef"){ //looks like 3 bytes UTF8 |
| 212 |
if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf"){ //yeah, almost sure it's UTF8 already |
| 213 |
$buf .= $c1 . $c2 . $c3; |
| 214 |
$i = $i + 2; |
| 215 |
} else { //not valid UTF8. Convert it. |
| 216 |
$cc1 = (chr(ord($c1) / 64) | "\xc0"); |
| 217 |
$cc2 = ($c1 & "\x3f") | "\x80"; |
| 218 |
$buf .= $cc1 . $cc2; |
| 219 |
} |
| 220 |
} elseif($c1 >= "\xf0" & $c1 <= "\xf7"){ //looks like 4 bytes UTF8 |
| 221 |
if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf" && $c4 >= "\x80" && $c4 <= "\xbf"){ //yeah, almost sure it's UTF8 already |
| 222 |
$buf .= $c1 . $c2 . $c3 . $c4; |
| 223 |
$i = $i + 3; |
| 224 |
} else { //not valid UTF8. Convert it. |
| 225 |
$cc1 = (chr(ord($c1) / 64) | "\xc0"); |
| 226 |
$cc2 = ($c1 & "\x3f") | "\x80"; |
| 227 |
$buf .= $cc1 . $cc2; |
| 228 |
} |
| 229 |
} else { //doesn't look like UTF8, but should be converted |
| 230 |
$cc1 = (chr(ord($c1) / 64) | "\xc0"); |
| 231 |
$cc2 = (($c1 & "\x3f") | "\x80"); |
| 232 |
$buf .= $cc1 . $cc2; |
| 233 |
} |
| 234 |
} elseif(($c1 & "\xc0") === "\x80"){ // needs conversion |
| 235 |
if(isset(self::$win1252ToUtf8[ord($c1)])) { //found in Windows-1252 special cases |
| 236 |
$buf .= self::$win1252ToUtf8[ord($c1)]; |
| 237 |
} else { |
| 238 |
$cc1 = (chr(ord($c1) / 64) | "\xc0"); |
| 239 |
$cc2 = (($c1 & "\x3f") | "\x80"); |
| 240 |
$buf .= $cc1 . $cc2; |
| 241 |
} |
| 242 |
} else { // it doesn't need conversion |
| 243 |
$buf .= $c1; |
| 244 |
} |
| 245 |
} |
| 246 |
return $buf; |
| 247 |
} |
| 248 |
|
| 249 |
static function toWin1252($text, $option = self::WITHOUT_ICONV) { |
| 250 |
if(is_array($text)) { |
| 251 |
foreach($text as $k => $v) { |
| 252 |
$text[$k] = self::toWin1252($v, $option); |
| 253 |
} |
| 254 |
return $text; |
| 255 |
} elseif(is_string($text)) { |
| 256 |
return static::utf8_decode($text, $option); |
| 257 |
} else { |
| 258 |
return $text; |
| 259 |
} |
| 260 |
} |
| 261 |
|
| 262 |
static function toISO8859($text, $option = self::WITHOUT_ICONV) { |
| 263 |
return self::toWin1252($text, $option); |
| 264 |
} |
| 265 |
|
| 266 |
static function toLatin1($text, $option = self::WITHOUT_ICONV) { |
| 267 |
return self::toWin1252($text, $option); |
| 268 |
} |
| 269 |
|
| 270 |
static function fixUTF8($text, $option = self::WITHOUT_ICONV){ |
| 271 |
if(is_array($text)) { |
| 272 |
foreach($text as $k => $v) { |
| 273 |
$text[$k] = self::fixUTF8($v, $option); |
| 274 |
} |
| 275 |
return $text; |
| 276 |
} |
| 277 |
|
| 278 |
if(!is_string($text)) { |
| 279 |
return $text; |
| 280 |
} |
| 281 |
|
| 282 |
$last = ""; |
| 283 |
while($last <> $text){ |
| 284 |
$last = $text; |
| 285 |
$text = self::toUTF8(static::utf8_decode($text, $option)); |
| 286 |
} |
| 287 |
$text = self::toUTF8(static::utf8_decode($text, $option)); |
| 288 |
return $text; |
| 289 |
} |
| 290 |
|
| 291 |
static function UTF8FixWin1252Chars($text){ |
| 292 |
// If you received an UTF-8 string that was converted from Windows-1252 as it was ISO8859-1 |
| 293 |
// (ignoring Windows-1252 chars from 80 to 9F) use this function to fix it. |
| 294 |
// See: http://en.wikipedia.org/wiki/Windows-1252 |
| 295 |
|
| 296 |
return str_replace(array_keys(self::$brokenUtf8ToUtf8), array_values(self::$brokenUtf8ToUtf8), $text); |
| 297 |
} |
| 298 |
|
| 299 |
static function removeBOM($str=""){ |
| 300 |
if(substr($str, 0,3) === pack("CCC",0xef,0xbb,0xbf)) { |
| 301 |
$str=substr($str, 3); |
| 302 |
} |
| 303 |
return $str; |
| 304 |
} |
| 305 |
|
| 306 |
protected static function strlen($text){ |
| 307 |
return (function_exists('mb_strlen') && ((int) ini_get('mbstring.func_overload')) & 2) ? |
| 308 |
mb_strlen($text,'8bit') : strlen($text); |
| 309 |
} |
| 310 |
|
| 311 |
public static function normalizeEncoding($encodingLabel) |
| 312 |
{ |
| 313 |
$encoding = strtoupper($encodingLabel); |
| 314 |
$encoding = preg_replace('/[^a-zA-Z0-9\s]/', '', $encoding); |
| 315 |
$equivalences = array( |
| 316 |
'ISO88591' => 'ISO-8859-1', |
| 317 |
'ISO8859' => 'ISO-8859-1', |
| 318 |
'ISO' => 'ISO-8859-1', |
| 319 |
'LATIN1' => 'ISO-8859-1', |
| 320 |
'LATIN' => 'ISO-8859-1', |
| 321 |
'UTF8' => 'UTF-8', |
| 322 |
'UTF' => 'UTF-8', |
| 323 |
'WIN1252' => 'ISO-8859-1', |
| 324 |
'WINDOWS1252' => 'ISO-8859-1' |
| 325 |
); |
| 326 |
|
| 327 |
if(empty($equivalences[$encoding])){ |
| 328 |
return 'UTF-8'; |
| 329 |
} |
| 330 |
|
| 331 |
return $equivalences[$encoding]; |
| 332 |
} |
| 333 |
|
| 334 |
public static function encode($encodingLabel, $text) |
| 335 |
{ |
| 336 |
$encodingLabel = self::normalizeEncoding($encodingLabel); |
| 337 |
if($encodingLabel === 'ISO-8859-1') return self::toLatin1($text); |
| 338 |
return self::toUTF8($text); |
| 339 |
} |
| 340 |
|
| 341 |
protected static function utf8_decode($text, $option = self::WITHOUT_ICONV) |
| 342 |
{ |
| 343 |
if ($option == self::WITHOUT_ICONV || !function_exists('iconv')) { |
| 344 |
$o = utf8_decode( |
| 345 |
str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), self::toUTF8($text)) |
| 346 |
); |
| 347 |
} else { |
| 348 |
$o = iconv("UTF-8", "Windows-1252" . ($option === self::ICONV_TRANSLIT ? '//TRANSLIT' : ($option === self::ICONV_IGNORE ? '//IGNORE' : '')), $text); |
| 349 |
} |
| 350 |
return $o; |
| 351 |
} |
| 352 |
} |
| 353 |
|