Изменить кодировку файла php
Is it possible to convert a file into UTF-8 on my end?
If I have an access on the file after the submission with
But the problem is, this code remove special characters like single quote.
I put it for additional information. Thanks for those who can help!
checkout the iconv extension for PHP, it has some pretty good mechanisms for converting character encoding, much better than the mb_family of functions.
5 Answers 5
before you can convert it to utf-8, you need to know what characterset it is. if you can't figure that out, you can't in any sane way convert it to utf8.. however, an insane way to convert it to utf-8, if the encoding cannot be determined, is to simply strip any bytes that doesn't happen to be valid in utf-8, you might be able to use that as a fallback.
warning, untested code (im suddenly in a hurry), but may look something like this:
Try this out.
The example I have used was something I was doing in a test environment, you might need to change the code slightly.
I had a text file with the following data in:
Then I had a form which took a file input in and performed the following code:
The function neatify_files is something I wrote to make the $_FILES array more logical in its layout.
The form is a standard form that simply POST s the data to the server.
Note: Using $_SERVER["PHP_SELF"] is a security risk, see here for more.
When the data is posted I store the file in a variable. Obviously, if you are using the multiple attribute your code won't look quite like this.
$handle stores the entire contents of the text file, in a read-only format; hence the "r" argument.
$enc uses the mb_detect_encoding function to detect the encoding (duh).
At first I was having trouble with obtaining the correct encoding. Setting the encoding_list to use only UTF-8, and setting strict to be true.
If the encoding is UTF-8 then I simply print the line, if it didn't I converted it to UTF-8 using the iconv function.
Преобразует строку string из кодировки from_encoding в to_encoding .
Возвращаемые значения
Возвращает преобразованную строку или false в случае возникновения ошибки.
User Contributed Notes 39 notes
The "//ignore" option doesn't work with recent versions of the iconv library. So if you're having trouble with that option, you aren't alone.
That means you can't currently use this function to filter invalid characters. Instead it silently fails and returns an empty string (or you'll get a notice but only if you have E_NOTICE enabled).
This has been a known bug with a known solution for at least since 2009 years but no one seems to be willing to fix it (PHP must pass the -c option to iconv). It's still broken as of the latest release 5.4.3.
[UPDATE 15-JUN-2012]
Here's a workaround.
ini_set('mbstring.substitute_character', "none");
$text= mb_convert_encoding($text, 'UTF-8', 'UTF-8');
That will strip invalid characters from UTF-8 strings (so that you can insert it into a database, etc.). Instead of "none" you can also use the value 32 if you want it to insert spaces in place of the invalid characters.
Please note that iconv('UTF-8', 'ASCII//TRANSLIT', . ) doesn't work properly when locale category LC_CTYPE is set to C or POSIX. You must choose another locale otherwise all non-ASCII characters will be replaced with question marks. This is at least true with glibc 2.5.
Example:
setlocale ( LC_CTYPE , 'POSIX' );
echo iconv ( 'UTF-8' , 'ASCII//TRANSLIT' , "Žluťoučký kůň\n" );
// ?lu?ou?k? k??
setlocale ( LC_CTYPE , 'cs_CZ' );
echo iconv ( 'UTF-8' , 'ASCII//TRANSLIT' , "Žluťoučký kůň\n" );
// Zlutoucky kun
?>
Interestingly, setting different target locales results in different, yet appropriate, transliterations. For example:
//some German
$utf8_sentence = 'Weiß, Goldmann, Göbel, Weiss, Göthe, Goethe und Götz' ;
//UK
setlocale ( LC_ALL , 'en_GB' );
//transliterate
$trans_sentence = iconv ( 'UTF-8' , 'ASCII//TRANSLIT' , $utf8_sentence );
//gives [Weiss, Goldmann, Gobel, Weiss, Gothe, Goethe und Gotz]
//which is our original string flattened into 7-bit ASCII as
//an English speaker would do it (ie. simply remove the umlauts)
echo $trans_sentence . PHP_EOL ;
//Germany
setlocale ( LC_ALL , 'de_DE' );
$trans_sentence = iconv ( 'UTF-8' , 'ASCII//TRANSLIT' , $utf8_sentence );
//gives [Weiss, Goldmann, Goebel, Weiss, Goethe, Goethe und Goetz]
//which is exactly how a German would transliterate those
//umlauted characters if forced to use 7-bit ASCII!
//(because really ä = ae, ö = oe and ü = ue)
echo $trans_sentence . PHP_EOL ;
to test different combinations of convertions between charsets (when we don't know the source charset and what is the convenient destination charset) this is an example :
$tab = array( "UTF-8" , "ASCII" , "Windows-1252" , "ISO-8859-15" , "ISO-8859-1" , "ISO-8859-6" , "CP1256" );
$chain = "" ;
foreach ( $tab as $i )
<
foreach ( $tab as $j )
<
$chain .= " $i$j " . iconv ( $i , $j , " $my_string " );
>
>
echo $chain ;
?>
then after displaying, you use the $i$j that shows good displaying.
NB: you can add other charsets to $tab to test other cases.
If you want to convert to a Unicode encoding without the byte order mark (BOM), add the endianness to the encoding, e.g. instead of "UTF-16" which will add a BOM to the start of the string, use "UTF-16BE" which will convert the string without adding a BOM.
iconv ( 'CP1252' , 'UTF-16' , $text ); // with BOM
iconv ( 'CP1252' , 'UTF-16BE' , $text ); // without BOM
Like many other people, I have encountered massive problems when using iconv() to convert between encodings (from UTF-8 to ISO-8859-15 in my case), especially on large strings.
The main problem here is that when your string contains illegal UTF-8 characters, there is no really straight forward way to handle those. iconv() simply (and silently!) terminates the string when encountering the problematic characters (also if using //IGNORE), returning a clipped string. The
$newstring = html_entity_decode ( htmlentities ( $oldstring , ENT_QUOTES , 'UTF-8' ), ENT_QUOTES , 'ISO-8859-15' );
?>
workaround suggested here and elsewhere will also break when encountering illegal characters, at least dropping a useful note ("htmlentities(): Invalid multibyte sequence in argument in. ")
I have found a lot of hints, suggestions and alternative methods (it's scary and in my opinion no good sign how many ways PHP natively provides to convert the encoding of strings), but none of them really worked, except for this one:
$newstring = mb_convert_encoding ( $oldstring , 'ISO-8859-15' , 'UTF-8' );
If you are getting question-marks in your iconv output when transliterating, be sure to 'setlocale' to something your system supports.
Some PHP CMS's will default setlocale to 'C', this can be a problem.
use the "locale" command to find out a list..
setlocale ( LC_CTYPE , 'en_AU.utf8' );
$str = iconv ( 'UTF-8' , 'ASCII//TRANSLIT' , "Côte d'Ivoire" );
?>
Here is how to convert UCS-2 numbers to UTF-8 numbers in hex:
function ucs2toutf8 ( $str )
<
for ( $i = 0 ; $i < strlen ( $str ); $i += 4 )
<
$substring1 = $str [ $i ]. $str [ $i + 1 ];
$substring2 = $str [ $i + 2 ]. $str [ $i + 3 ];
if ( $substring1 == "00" )
<
$byte1 = "" ;
$byte2 = $substring2 ;
>
else
<
$substring = $substring1 . $substring2 ;
$byte1 = dechex ( 192 +( hexdec ( $substring )/ 64 ));
$byte2 = dechex ( 128 +( hexdec ( $substring )% 64 ));
>
$utf8 .= $byte1 . $byte2 ;
>
return $utf8 ;
>
echo strtoupper ( ucs2toutf8 ( "06450631062D0020" ));
?>
Input:
06450631062D
Output:
D985D8B1D8AD
As orrd101 said, there is a bug with //IGNORE in recent PHP versions (we use 5.6.5) where we couldn't convert some strings (i.e. "∙" from UTF8 to CP1251 with //IGNORE).
But we have found a workaround and now we use both //TRANSLIT and //IGNORE flags:
$text="∙";
iconv("UTF8", "CP1251//TRANSLIT//IGNORE", $text);
I just found out today that the Windows and *NIX versions of PHP use different iconv libraries and are not very consistent with each other.
Here is a repost of my earlier code that now works on more systems. It converts as much as possible and replaces the rest with question marks:
if (! function_exists ( 'utf8_to_ascii' )) setlocale ( LC_CTYPE , 'en_AU.utf8' );
if (@ iconv ( "UTF-8" , "ASCII//IGNORE//TRANSLIT" , 'é' ) === false ) // PHP is probably using the glibc library (*NIX)
function utf8_to_ascii ( $text ) return iconv ( "UTF-8" , "ASCII//TRANSLIT" , $text );
>
>
else // PHP is probably using the libiconv library (Windows)
function utf8_to_ascii ( $text ) if ( is_string ( $text )) // Includes combinations of characters that present as a single glyph
$text = preg_replace_callback ( '/\X/u' , __FUNCTION__ , $text );
>
elseif ( is_array ( $text ) && count ( $text ) == 1 && is_string ( $text [ 0 ])) // IGNORE characters that can't be TRANSLITerated to ASCII
$text = iconv ( "UTF-8" , "ASCII//IGNORE//TRANSLIT" , $text [ 0 ]);
// The documentation says that iconv() returns false on failure but it returns ''
if ( $text === '' || ! is_string ( $text )) $text = '?' ;
>
elseif ( preg_match ( '/\w/' , $text )) < // If the text contains any letters.
$text = preg_replace ( '/\W+/' , '' , $text ); // . then remove all non-letters
>
>
else < // $text was not a string
$text = '' ;
>
return $text ;
>
>
>
iconv with //IGNORE works as expected: it will skip the character if this one does not exist in the $out_charset encoding.
If a character is missing from the $in_charset encoding (eg byte \x81 from CP1252 encoding), then iconv will return an error, whether with //IGNORE or not.
There may be situations when a new version of a web site, all in UTF-8, has to display some old data remaining in the database with ISO-8859-1 accents. The problem is iconv("ISO-8859-1", "UTF-8", $string) should not be applied if $string is already UTF-8 encoded.
I use this function that does'nt need any extension :
I have not tested it extensively, hope it may help.
Here is an example how to convert windows-1251 (windows) or cp1251(Linux/Unix) encoded string to UTF-8 encoding.
Преобразует параметр string из кодировки to_encoding или текущей внутренней кодировки в to_encoding . Также можно указать необязательный параметр from_encoding . Если string является массивом ( array ), все его строковые ( string ) значения будут преобразованы рекурсивно.
User Contributed Notes 33 notes
For my last project I needed to convert several CSV files from Windows-1250 to UTF-8, and after several days of searching around I found a function that is partially solved my problem, but it still has not transformed all the characters. So I made this:
I've been trying to find the charset of a norwegian (with a lot of ø, æ, å) txt file written on a Mac, i've found it in this way:
$text = "A strange string to pass, maybe with some ø, æ, å characters." ;
foreach( mb_list_encodings () as $chr ) <
echo mb_convert_encoding ( $text , 'UTF-8' , $chr ). " : " . $chr . "
" ;
>
?>
The line that looks good, gives you the encoding it was written in.
Hope can help someone
many people below talk about using
mb_convert_encode ( $s , 'HTML-ENTITIES' , 'UTF-8' );
?>
to convert non-ascii code into html-readable stuff. Due to my webserver being out of my control, I was unable to set the database character set, and whenever PHP made a copy of my $s variable that it had pulled out of the database, it would convert it to nasty latin1 automatically and not leave it in it's beautiful UTF-8 glory.
So [insert korean characters here] turned into .
I found myself needing to pass by reference (which of course is deprecated/nonexistent in recent versions of PHP)
so instead of
mb_convert_encode (& $s , 'HTML-ENTITIES' , 'UTF-8' );
?>
which worked perfectly until I upgraded, so I had to use
call_user_func_array ( 'mb_convert_encoding' , array(& $s , 'HTML-ENTITIES' , 'UTF-8' ));
?>
Hope it helps someone else out
public function encodeToIso($string) return mb_convert_encoding($string, "ISO-8859-1", mb_detect_encoding($string, "UTF-8, ISO-8859-1, ISO-8859-15", true));
>
For me these functions are working fine. Give it a try
aaron, to discard unsupported characters instead of printing a ?, you might as well simply set the configuration directive:
in your php.ini. Be sure to include the quotes around none. Or at run-time with
ini_set ( 'mbstring.substitute_character' , "none" );
?>
My solution below was slightly incorrect, so here is the correct version (I posted at the end of a long day, never a good idea!)
Again, this is a quick and dirty solution to stop mb_convert_encoding from filling your string with question marks whenever it encounters an illegal character for the target encoding.
function convert_to ( $source , $target_encoding )
// detect the character encoding of the incoming file
$encoding = mb_detect_encoding ( $source , "auto" );
// escape all of the question marks so we can remove artifacts from
// the unicode conversion process
$target = str_replace ( "?" , "[question_mark]" , $source );
// convert the string to the target encoding
$target = mb_convert_encoding ( $target , $target_encoding , $encoding );
// remove any question marks that have been introduced because of illegal characters
$target = str_replace ( "?" , "" , $target );
// replace the token string "[question_mark]" with the symbol "?"
$target = str_replace ( "[question_mark]" , "?" , $target );
return $target ;
>
?>
Hope this helps someone! (Admins should feel free to delete my previous, incorrect, post for clarity)
-A
Another sample of recoding without MultiByte enabling.
(Russian koi->win, if input in win-encoding already, function recode() returns unchanged string)
// 0 - win
// 1 - koi
function detect_encoding ( $str ) $win = 0 ;
$koi = 0 ;
for( $i = 0 ; $i < strlen ( $str ); $i ++) if( ord ( $str [ $i ]) > 224 && ord ( $str [ $i ]) < 255 ) $win ++;
if( ord ( $str [ $i ]) > 192 && ord ( $str [ $i ]) < 223 ) $koi ++;
>
if( $win < $koi ) return 1 ;
> else return 0 ;
// recodes koi to win
function koi_to_win ( $string )
$kw = array( 128 , 129 , 130 , 131 , 132 , 133 , 134 , 135 , 136 , 137 , 138 , 139 , 140 , 141 , 142 , 143 , 144 , 145 , 146 , 147 , 148 , 149 , 150 , 151 , 152 , 153 , 154 , 155 , 156 , 157 , 158 , 159 , 160 , 161 , 162 , 163 , 164 , 165 , 166 , 167 , 168 , 169 , 170 , 171 , 172 , 173 , 174 , 175 , 176 , 177 , 178 , 179 , 180 , 181 , 182 , 183 , 184 , 185 , 186 , 187 , 188 , 189 , 190 , 191 , 254 , 224 , 225 , 246 , 228 , 229 , 244 , 227 , 245 , 232 , 233 , 234 , 235 , 236 , 237 , 238 , 239 , 255 , 240 , 241 , 242 , 243 , 230 , 226 , 252 , 251 , 231 , 248 , 253 , 249 , 247 , 250 , 222 , 192 , 193 , 214 , 196 , 197 , 212 , 195 , 213 , 200 , 201 , 202 , 203 , 204 , 205 , 206 , 207 , 223 , 208 , 209 , 210 , 211 , 198 , 194 , 220 , 219 , 199 , 216 , 221 , 217 , 215 , 218 );
$wk = array( 128 , 129 , 130 , 131 , 132 , 133 , 134 , 135 , 136 , 137 , 138 , 139 , 140 , 141 , 142 , 143 , 144 , 145 , 146 , 147 , 148 , 149 , 150 , 151 , 152 , 153 , 154 , 155 , 156 , 157 , 158 , 159 , 160 , 161 , 162 , 163 , 164 , 165 , 166 , 167 , 168 , 169 , 170 , 171 , 172 , 173 , 174 , 175 , 176 , 177 , 178 , 179 , 180 , 181 , 182 , 183 , 184 , 185 , 186 , 187 , 188 , 189 , 190 , 191 , 225 , 226 , 247 , 231 , 228 , 229 , 246 , 250 , 233 , 234 , 235 , 236 , 237 , 238 , 239 , 240 , 242 , 243 , 244 , 245 , 230 , 232 , 227 , 254 , 251 , 253 , 255 , 249 , 248 , 252 , 224 , 241 , 193 , 194 , 215 , 199 , 196 , 197 , 214 , 218 , 201 , 202 , 203 , 204 , 205 , 206 , 207 , 208 , 210 , 211 , 212 , 213 , 198 , 200 , 195 , 222 , 219 , 221 , 223 , 217 , 216 , 220 , 192 , 209 );
$end = strlen ( $string );
$pos = 0 ;
do $c = ord ( $string [ $pos ]);
if ( $c > 128 ) $string [ $pos ] = chr ( $kw [ $c - 128 ]);
>
function recode ( $str )
$enc = detect_encoding ( $str );
if ( $enc == 1 ) $str = koi_to_win ( $str );
>
If the string //TRANSLIT is appended to to_encoding , then transliteration is activated. This means that when a character can't be represented in the target charset, it may be approximated through one or several similarly looking characters. If the string //IGNORE is appended, characters that cannot be represented in the target charset are silently discarded. Otherwise, E_NOTICE is generated and the function will return false .
If and how //TRANSLIT works exactly depends on the system's iconv() implementation (cf. ICONV_IMPL ). Some implementations are known to ignore //TRANSLIT , so the conversion is likely to fail for characters which are illegal for the to_encoding .
The string to be converted.
Examples
echo 'Original : ' , $text , PHP_EOL ;
echo 'TRANSLIT : ' , iconv ( "UTF-8" , "ISO-8859-1//TRANSLIT" , $text ), PHP_EOL ;
echo 'IGNORE : ' , iconv ( "UTF-8" , "ISO-8859-1//IGNORE" , $text ), PHP_EOL ;
echo 'Plain : ' , iconv ( "UTF-8" , "ISO-8859-1" , $text ), PHP_EOL ;
The above example will output something similar to:
User Contributed Notes 39 notes
The "//ignore" option doesn't work with recent versions of the iconv library. So if you're having trouble with that option, you aren't alone.
That means you can't currently use this function to filter invalid characters. Instead it silently fails and returns an empty string (or you'll get a notice but only if you have E_NOTICE enabled).
This has been a known bug with a known solution for at least since 2009 years but no one seems to be willing to fix it (PHP must pass the -c option to iconv). It's still broken as of the latest release 5.4.3.
[UPDATE 15-JUN-2012]
Here's a workaround.
ini_set('mbstring.substitute_character', "none");
$text= mb_convert_encoding($text, 'UTF-8', 'UTF-8');
That will strip invalid characters from UTF-8 strings (so that you can insert it into a database, etc.). Instead of "none" you can also use the value 32 if you want it to insert spaces in place of the invalid characters.
Please note that iconv('UTF-8', 'ASCII//TRANSLIT', . ) doesn't work properly when locale category LC_CTYPE is set to C or POSIX. You must choose another locale otherwise all non-ASCII characters will be replaced with question marks. This is at least true with glibc 2.5.
Example:
setlocale ( LC_CTYPE , 'POSIX' );
echo iconv ( 'UTF-8' , 'ASCII//TRANSLIT' , "Žluťoučký kůň\n" );
// ?lu?ou?k? k??
setlocale ( LC_CTYPE , 'cs_CZ' );
echo iconv ( 'UTF-8' , 'ASCII//TRANSLIT' , "Žluťoučký kůň\n" );
// Zlutoucky kun
?>
Interestingly, setting different target locales results in different, yet appropriate, transliterations. For example:
//some German
$utf8_sentence = 'Weiß, Goldmann, Göbel, Weiss, Göthe, Goethe und Götz' ;
//UK
setlocale ( LC_ALL , 'en_GB' );
//transliterate
$trans_sentence = iconv ( 'UTF-8' , 'ASCII//TRANSLIT' , $utf8_sentence );
//gives [Weiss, Goldmann, Gobel, Weiss, Gothe, Goethe und Gotz]
//which is our original string flattened into 7-bit ASCII as
//an English speaker would do it (ie. simply remove the umlauts)
echo $trans_sentence . PHP_EOL ;
//Germany
setlocale ( LC_ALL , 'de_DE' );
$trans_sentence = iconv ( 'UTF-8' , 'ASCII//TRANSLIT' , $utf8_sentence );
//gives [Weiss, Goldmann, Goebel, Weiss, Goethe, Goethe und Goetz]
//which is exactly how a German would transliterate those
//umlauted characters if forced to use 7-bit ASCII!
//(because really ä = ae, ö = oe and ü = ue)
echo $trans_sentence . PHP_EOL ;
to test different combinations of convertions between charsets (when we don't know the source charset and what is the convenient destination charset) this is an example :
$tab = array( "UTF-8" , "ASCII" , "Windows-1252" , "ISO-8859-15" , "ISO-8859-1" , "ISO-8859-6" , "CP1256" );
$chain = "" ;
foreach ( $tab as $i )
<
foreach ( $tab as $j )
<
$chain .= " $i$j " . iconv ( $i , $j , " $my_string " );
>
>
echo $chain ;
?>
then after displaying, you use the $i$j that shows good displaying.
NB: you can add other charsets to $tab to test other cases.
If you want to convert to a Unicode encoding without the byte order mark (BOM), add the endianness to the encoding, e.g. instead of "UTF-16" which will add a BOM to the start of the string, use "UTF-16BE" which will convert the string without adding a BOM.
iconv ( 'CP1252' , 'UTF-16' , $text ); // with BOM
iconv ( 'CP1252' , 'UTF-16BE' , $text ); // without BOM
Like many other people, I have encountered massive problems when using iconv() to convert between encodings (from UTF-8 to ISO-8859-15 in my case), especially on large strings.
The main problem here is that when your string contains illegal UTF-8 characters, there is no really straight forward way to handle those. iconv() simply (and silently!) terminates the string when encountering the problematic characters (also if using //IGNORE), returning a clipped string. The
$newstring = html_entity_decode ( htmlentities ( $oldstring , ENT_QUOTES , 'UTF-8' ), ENT_QUOTES , 'ISO-8859-15' );
?>
workaround suggested here and elsewhere will also break when encountering illegal characters, at least dropping a useful note ("htmlentities(): Invalid multibyte sequence in argument in. ")
I have found a lot of hints, suggestions and alternative methods (it's scary and in my opinion no good sign how many ways PHP natively provides to convert the encoding of strings), but none of them really worked, except for this one:
$newstring = mb_convert_encoding ( $oldstring , 'ISO-8859-15' , 'UTF-8' );
If you are getting question-marks in your iconv output when transliterating, be sure to 'setlocale' to something your system supports.
Some PHP CMS's will default setlocale to 'C', this can be a problem.
use the "locale" command to find out a list..
setlocale ( LC_CTYPE , 'en_AU.utf8' );
$str = iconv ( 'UTF-8' , 'ASCII//TRANSLIT' , "Côte d'Ivoire" );
?>
Here is how to convert UCS-2 numbers to UTF-8 numbers in hex:
function ucs2toutf8 ( $str )
<
for ( $i = 0 ; $i < strlen ( $str ); $i += 4 )
<
$substring1 = $str [ $i ]. $str [ $i + 1 ];
$substring2 = $str [ $i + 2 ]. $str [ $i + 3 ];
if ( $substring1 == "00" )
<
$byte1 = "" ;
$byte2 = $substring2 ;
>
else
<
$substring = $substring1 . $substring2 ;
$byte1 = dechex ( 192 +( hexdec ( $substring )/ 64 ));
$byte2 = dechex ( 128 +( hexdec ( $substring )% 64 ));
>
$utf8 .= $byte1 . $byte2 ;
>
return $utf8 ;
>
echo strtoupper ( ucs2toutf8 ( "06450631062D0020" ));
?>
Input:
06450631062D
Output:
D985D8B1D8AD
As orrd101 said, there is a bug with //IGNORE in recent PHP versions (we use 5.6.5) where we couldn't convert some strings (i.e. "∙" from UTF8 to CP1251 with //IGNORE).
But we have found a workaround and now we use both //TRANSLIT and //IGNORE flags:
$text="∙";
iconv("UTF8", "CP1251//TRANSLIT//IGNORE", $text);
I just found out today that the Windows and *NIX versions of PHP use different iconv libraries and are not very consistent with each other.
Here is a repost of my earlier code that now works on more systems. It converts as much as possible and replaces the rest with question marks:
if (! function_exists ( 'utf8_to_ascii' )) setlocale ( LC_CTYPE , 'en_AU.utf8' );
if (@ iconv ( "UTF-8" , "ASCII//IGNORE//TRANSLIT" , 'é' ) === false ) // PHP is probably using the glibc library (*NIX)
function utf8_to_ascii ( $text ) return iconv ( "UTF-8" , "ASCII//TRANSLIT" , $text );
>
>
else // PHP is probably using the libiconv library (Windows)
function utf8_to_ascii ( $text ) if ( is_string ( $text )) // Includes combinations of characters that present as a single glyph
$text = preg_replace_callback ( '/\X/u' , __FUNCTION__ , $text );
>
elseif ( is_array ( $text ) && count ( $text ) == 1 && is_string ( $text [ 0 ])) // IGNORE characters that can't be TRANSLITerated to ASCII
$text = iconv ( "UTF-8" , "ASCII//IGNORE//TRANSLIT" , $text [ 0 ]);
// The documentation says that iconv() returns false on failure but it returns ''
if ( $text === '' || ! is_string ( $text )) $text = '?' ;
>
elseif ( preg_match ( '/\w/' , $text )) < // If the text contains any letters.
$text = preg_replace ( '/\W+/' , '' , $text ); // . then remove all non-letters
>
>
else < // $text was not a string
$text = '' ;
>
return $text ;
>
>
>
iconv with //IGNORE works as expected: it will skip the character if this one does not exist in the $out_charset encoding.
If a character is missing from the $in_charset encoding (eg byte \x81 from CP1252 encoding), then iconv will return an error, whether with //IGNORE or not.
There may be situations when a new version of a web site, all in UTF-8, has to display some old data remaining in the database with ISO-8859-1 accents. The problem is iconv("ISO-8859-1", "UTF-8", $string) should not be applied if $string is already UTF-8 encoded.
I use this function that does'nt need any extension :
I have not tested it extensively, hope it may help.
Here is an example how to convert windows-1251 (windows) or cp1251(Linux/Unix) encoded string to UTF-8 encoding.
Здравствуйте, этим постом я хотел бы попытаться приблизить светлое будущее, в котором все используют «кошерную» кодировку UTF-8. В частности это касается наиболее близкой мне среды – веба и языка программирования – PHP, а в конце серии мы подойдём к практической части и разработаем ещё одну велосипедную библиотеку.
1. Вступление
Для понимания дальнейшего текста начинающим нужно знать некоторые детали по кодировкам в целом. Подачу материала я постараюсь максимально упростить. Для незнающих ничего о побитовых операциях необходимо предварительно ознакомиться с материалами на википедии.
Начать нужно с понимания того, что компьютер работает с числами и хранить строку (и символ, как её часть) приходиться тоже в числовом виде. Для этих целей существуют кодировки. По сути это таблицы, в которых указано соответствие между числами и символами. Исторически сложилось, что основная кодировка ASCII содержит лишь контрольные коды и латинские символы, всего их 128 (127 – максимальное число, которое можно хранить в 7 битах).
В итоге потребовался универсальный выход, кодировка, которая сможет хранить все возможные символы и будет учитывать различия в письме различных народов (например, направление письма). Поставленную задачу решили созданием Unicode, которая способна кодировать практически все системы письменности в мире одной кодировкой.
- полная совместимость с ASCII;
- её можно с высокой точностью отличить от других кодировок;
- каждый символ может занимать от 1 до 4 байт (в стандарте байты называют октетами; внимание, я могу заменять эти термины друг другом!) в зависимости от числового значения, которое нужно хранить.
Хотелось бы подробнее остановиться на последнем пункте. Это значит, что если раньше можно было выполнять простое преобразование по таблице и записывать результат, то сейчас определён и метод сохранения этого результата, в зависимости от разрядности, которая требуется для его хранения. На примере принцип хранения вы можете увидеть в таблице (x – хранимые биты данных):
Бит | Максимальное хранимое значение | 1 октет | 2 октет | 3 октет | 4 октет |
---|---|---|---|---|---|
Начальный октет | Продолжающие октеты | ||||
7 | U+007F | 0xxxxxxx | |||
11 | U+07FF | 110xxxxx | 10xxxxxx | ||
16 | U+FFFF | 1110xxxx | 10xxxxxx | 10xxxxxx | |
21 | U+10FFFF (по стандарту, но реально U+1FFFFF) | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
Легко заметить, что в старших битах начального октета всегда находится счётчик, указывающий на количество байт в последовательности – это количество ведущих единиц, после которых идёт ноль. Обратите внимание: если октет лишь один, то ведущая единица не указывается, благодаря чему начальные октеты легко отличить от продолжающих.
Для примера давайте посмотрим как строка «Привет Hi» будет выглядеть в кодировке UTF-8.
Шаг первый. Перевести каждый символ в его числовое представление (я буду использовать шестнадцатеричную систему исчисления) по таблице.
Привет Hi = 0x041F 0x0440 0x0438 0x0432 0x044D 0x0442 0x0020 0x0048 0x0069
Не забываем, что пробел – тоже символ.
Шаг второй. Конвертировать числа из шестнадцатеричной в двоичную систему. Используем калькулятор Windows 7 (в режиме программиста).
0x041F = 0000 0100 0001 1111
0x0440 = 0000 0100 0100 0000
0x0438 = 0000 0100 0011 1000
0x0432 = 0000 0100 0011 0010
0x0435 = 0000 0100 0011 0101
0x0442 = 0000 0100 0100 0010
0x0020 = 0010 0000
0x0048 = 0100 1000
0x0069 = 0110 1001
Для наглядности я добавил нули в старшие разряды. Обратите внимание: символы могут занимать разное количество байт.
Шаг третий. Перевести числовые представления в последовательности октетов UTF-8.
0x041F = 100 0001 1111 = 110xxxxx 10xxxxxx = 11010000 10011111
0x0440 = 100 0100 0000 = 110xxxxx 10xxxxxx = 11010001 10000000
0x0438 = 100 0011 1000 = 110xxxxx 10xxxxxx = 11010000 10111000
0x0432 = 100 0011 0010 = 110xxxxx 10xxxxxx = 11010000 10110010
0x0435 = 100 0011 0101 = 110xxxxx 10xxxxxx = 11010000 10110101
0x0442 = 100 0100 0010 = 110xxxxx 10xxxxxx = 11010001 10000010
0x0020 = 010 0000 = 0xxxxxx = 00100000
0x0048 = 100 1000 = 0xxxxxx = 01001000
0x0069 = 110 1001 = 0xxxxxx = 01101001
Счётчики выделены жирным. Обратите внимание: символы с кодами до 0x0080 сохраняются без изменений, это и есть совместимость с ASCII. Ещё следует понимать, что UTF-8 будет занимать в 2 раза больше места (2 байта) для русскоязычного текста, чем Windows-1251, которая использует лишь 1 байт.
В качестве решения можно записать всю последовательность подряд (надеюсь без ошибок): «11010000 10011111 11010001 10000000 11010000 10111000 11010000 10110010 11010000 10110101 11010001 10000010 00100000 01001000 01101001».
Проверить решение можно кодом:
$tmp = '' ;
foreach ( explode ( ' ' , '11010000 10011111 11010001 10000000 11010000 10111000 11010000 10110010 11010000 10110101 11010001 10000010 00100000 01001000 01101001' ) as $octet ) <
$tmp .= chr ( bindec ( $octet ) ) ;
>
echo $tmp ;
- Определить количество октетов в 1-ом символе и сохранить это значение;
- От первого байта отбросить счётчик октетов, остаток сохранить;
- Если в последовательности более 1 октета сдвигать остаток после операции 2 на 6 бит влево и записывать в них информацию с младших 6 бит последующего октета;
- Повторять с 1 пункта до удовлетворения :).
Оптимизированный PHP код, который позволяет получать числовое представление символов и обратную операцию (полную версию опубликую в конце цикла):
- class String_Multibyte
- /**
* Возвращает десятеричное значение UTF-8 символа, первый октет которого находится на позиции $index в строке $char.
* Суррогатные коды, символы с приватных зон, BOM и 0x10FFFE-0x10FFFF вернут FALSE.
*
* [. ] Функция была оптимизирована, потому содержит избыточный код.
*
* @author Andrew Dryga , .
* @param string $char Строка с символом (символами).
* @param int &$index Аргумент указывает на октет, в котором необходимо начать вычисление значение для символа. После вызова будет хранить позицию последнего октета, принадлежащего указанному символу.
* @return int|false Десятерчиное значение символа или FALSE в случае обнаружения символа или байта, которые нужно проигнорировать.
*/ - public function getCodePoint( $char , & $index = 0 )
- // Получаем значение первого октета
- $octet1 = ord( $char [ $index ]);
- // Если оно попадает в диапазон ASCII кодов (имеет вид 0bbb bbbb), то возвращаем результат.
- if ( $octet1 >> 7 == 0x00 )
- return $octet1 ;
- > elseif ( $octet1 >> 6 != 0x02 )
- // Проверяем существование следующего октета
- if (! isset ( $char [++ $index ]))
- return false ;
- >
- // Получаем его значение
- $octet2 = ord( $char [ $index ]);
- // Проверяем его на валидность (должен иметь вид 10bb bbbb)
- if ( $octet2 >> 6 != 0x02 )
- -- $index ;
- return false ;
- >
- // Оставляем только его нижние 6 бит
- $octet2 &= 0x3F ;
- // Проверяем счётчик и если октетов должно быть всего два, то формируем результат
- if ( $octet1 >> 5 == 0x06 )
- $result = ( $octet1 & 0x1F )
- // Результат должен быть в максимально сокращённой форме
- if ( 0x80 < $result )
- return $result ;
- >
- > else
- if (! isset ( $char [++ $index ]))
- return false ;
- >
- $octet3 = ord( $char [ $index ]);
- if ( $octet3 >> 6 != 0x02 )
- -- $index ;
- return false ;
- >
- $octet3 &= 0x3F ;
- if ( $octet1 >> 4 == 0x0E )
- $result = ( $octet1 & 0x0F )
- // Проверяем минимальное значение; удаляем суррогаты, приватную зону и BOM
- if ( 0x800 < $result && !( 0xD7FF < $result && $result < 0xF900 ) && $result != 0xFEFF )
- return $result ;
- >
- > else
- if (! isset ( $char [++ $index ]))
- return false ;
- >
- $octet4 = ord( $char [ $index ]);
- if ( $octet4 >> 6 != 0x02 )
- -- $index ;
- return false ;
- >
- $octet4 &= 0x3F ;
- if ( $octet1 >> 3 == 0x1E )
- $result = ( $octet1 & 0x07 )
- // Проверяем минимальное значение; Удаляем приватную зону и некоторые другие символы;
- // Удостовериваемся, что полученое значение не выходит за рамки зоны Unicode 10FFFF
- if ( 0x10000 < $result && $result < 0xF0000 )
- return $result ;
- >
- >
- >
- >
- return false ;
- >
- >
- /**
* Возвращает UTF-8 символ по его коду.
* [. ]
* @author ur001 , .
* @param string $codePoint Unicode character ordinal.
* @return string|FALSE UTF-8 символ или FALSE в случае ошибки.
*/ - public function getChar( $codePoint )
- if ( $codePoint < 0x80 )
- return chr( $codePoint );
- > elseif ( $codePoint < 0x800 )
- return chr( 0xC0 | $codePoint >> 6 ) . chr( 0x80 | $codePoint & 0x3F );
- > elseif ( $codePoint < 0x10000 )
- return chr( 0xE0 | $codePoint >> 12 ) . chr(
- 0x80 | $codePoint >> 6 & 0x3F ) . chr( 0x80 | $codePoint & 0x3F );
- > elseif ( $codePoint < 0x110000 )
- return chr( 0xF0 | $codePoint >> 18 ) . chr(
- 0x80 | $codePoint >> 12 & 0x3F ) . chr( 0x80 | $codePoint >> 6 & 0x3F ) . chr(
- 0x80 | $codePoint & 0x3F );
- > else
- return false ;
- >
- >
- >
Метод getChar() был взят с библиотеки Jevix, я всё-равно уже видел этот код, хорошо его запомнил и даже при его реализации по памяти было бы нечестно не упомянуть автора.
Вы же можете протестировать получившийся класс при помощи кода:
Я не старался писать самый красивый или правильный код для тестов, но при помощи него вы можете спокойно побитово менять значения символов и сразу видеть результат. Все невалидные последовательности будут проигнорированы, выводимая строка всегда валидна, но это ещё далеко не всё.
Чтобы быть уверенным, что текст не содержит ничего лишнего нужно удалить с него ненужные (непечатные, нарушающие разметку, неопределённые, суррогатные и т.п.) символы и провести нормализацию, об этом в следующей части.
Дальше будет про нормализацию, безопасность, определение кодировок и работу с UTF-8 в PHP.
Список параметров
Текущая кодировка, используемая для интерпретации параметра string .
Требуемая на выходе кодировка.
Если к параметру to_encoding добавлена строка //TRANSLIT , включается режим транслитерации. Это значит, что в случае, если символ не может быть представлен в требуемой кодировке, он может быть заменён одним или несколькими похожими символами. Если добавлена строка //IGNORE , то символы, которые не могут быть представлены в требуемой кодировке, будут удалены. В случае отсутствия вышеуказанных параметров будет сгенерирована ошибка уровня E_NOTICE , а функция вернёт false .
Как будет работать //TRANSLIT и будет ли вообще, зависит от системной реализации iconv() ( ICONV_IMPL ). Известны некоторые реализации, которые просто игнорируют //TRANSLIT , так что конвертация для символов некорректных для to_encoding скорее всего закончится ошибкой.
Строка ( string ) для преобразования.
Примеры
/* Преобразует строку в кодировку SJIS */
$str = mb_convert_encoding ( $str , "SJIS" );
/* Преобразует из EUC-JP в UTF-7 */
$str = mb_convert_encoding ( $str , "UTF-7" , "EUC-JP" );
/* Автоматически определяется кодировка среди JIS, eucjp-win, sjis-win, затем преобразуется в UCS-2LE */
$str = mb_convert_encoding ( $str , "UCS-2LE" , "JIS, eucjp-win, sjis-win" );
/* Если mbstring.language равен "Japanese", "auto" используется для обозначения "ASCII,JIS,UTF-8,EUC-JP,SJIS" */
$str = mb_convert_encoding ( $str , "EUC-JP" , "auto" );
?>
Возвращаемые значения
Преобразованная строка ( string ) или массив ( array ) или false в случае возникновения ошибки.
See Also
- mb_convert_encoding() - Convert a string from one character encoding to another
- UConverter::transcode() - Convert a string from one character encoding to another
Ошибки
Начиная с PHP 8.0.0, если значение to_encoding или from_encoding является недопустимой кодировкой, выбрасывается ValueError . До PHP 8.0.0 вместо этого выдавалась ошибка уровня E_WARNING .
Return Values
Returns the converted string, or false on failure.
Список параметров
Строка ( string ) или массив ( array ), для преобразования.
Требуемая кодировка результата.
Текущая кодировка, используемая для интерпретации строки string . Несколько кодировок могут быть указаны в виде массива ( array ) или в виде строки через запятую, в этом случае правильная кодировка будет определена по тому же алгоритму, что и в функции mb_detect_encoding() .
Если параметр from_encoding равен null или не указан, то будет использоваться mbstring.internal_encoding setting, если она установлена, иначе кодировка по умолчанию.
Допустимые значения to_encoding и from_encoding указаны на странице поддерживаемые кодировки.
Смотрите также
- mb_convert_encoding() - Преобразует строку из одной кодировки символов в другую
- UConverter::transcode() - Преобразует строку из одной кодировки символов в другую
Примеры
echo 'Исходная строка : ' , $text , PHP_EOL ;
echo 'С добавлением TRANSLIT : ' , iconv ( "UTF-8" , "ISO-8859-1//TRANSLIT" , $text ), PHP_EOL ;
echo 'С добавлением IGNORE : ' , iconv ( "UTF-8" , "ISO-8859-1//IGNORE" , $text ), PHP_EOL ;
echo 'Обычное преобразование : ' , iconv ( "UTF-8" , "ISO-8859-1" , $text ), PHP_EOL ;
Результатом выполнения данного примера будет что-то подобное:
Notes
Note:
The character encodings and options available depend on the installed implementation of iconv. If the argument to from_encoding or to_encoding is not supported on the current system, false will be returned.
Примечания
Замечание:
Доступные кодировки и опции зависят от установленной реализации iconv. Если параметр from_encoding или from_encoding не поддерживается в текущей системе, будет возвращено значение false .
Смотрите также
- mb_detect_order() - Установка/получение списка кодировок для механизмов определения кодировки
- UConverter::transcode() - Преобразует строку из одной кодировки символов в другую
- iconv() - Преобразует строку из одной кодировки символов в другую
Список изменений
8.0.0 | mb_convert_encoding() теперь выбрасывает ValueError , если передана недопустимая кодировка в to_encoding . |
---|---|
8.0.0 | mb_convert_encoding() теперь выбрасывает ValueError , если передана недопустимая кодировка в from_encoding . |
8.0.0 | Теперь from_encoding может быть null . |
Версия | Описание |
7.2.0 | Функция теперь также принимает массив ( array ) в string . Ранее поддерживались только строки ( string ). |
Читайте также: