I’m running Perl 5.10.0 and Postgres 8.4.3, and strings into a database, which is behind a DBIx::Class.
These strings should be in UTF-8, and therefore my database is running in UTF-8. Unfortunatly some of these strings are bad, containing malformed UTF-8, so when I run it I’m getting an exception
DBI Exception: DBD::Pg::st execute failed: ERROR: invalid byte sequence for encoding "UTF8": 0xb5
I thought that I could simply ignore the invalid ones, and worry about the malformed UTF-8 later, so using this code, it should flag and ignore the bad titles.
if(not utf8::valid($title)){
$title="Invalid UTF-8";
}
$data->title($title);
$data->update();
However Perl seems to think that the strings are valid, but it still throws the exceptions.
How can I get Perl to detect the bad UTF-8?
First off, please follow the documentation – the
utf8module should only be used in the ‘use utf8;’ form to indicate that your source code is UTF-8 instead of Latin-1. Don’t use any of the utf8 functions.Perl makes the distinction between bytes and UTF-8 strings. In byte mode, Perl doesn’t know or care what encoding you are using, and will use Latin-1 if you print it. Take for example the Euro sign (€). In UTF-8 this is 3 bytes, 0xE2, 0x82, 0xAC. If you print the length of these bytes, Perl will return 3. Again, it doesn’t care about the encoding. It can be any bytes or any encoding, legal or illegal.
If you use the
Encodemodule and callEncode::decode("UTF-8', $bytes)you will get a new string which has the so-called UTF8 flag set. Perl now knows your string is in UTF-8, and will return a length of 1.The problem that
utf8::validonly applies to the second type of string. Your strings are probably in the first form, byte mode, andutf8::validjust returns true for anything in byte form. This is documented in the perldoc.The solution is to get Perl to decode your byte strings as UTF-8, and detect any errors. This can be done with FB_CROAK as brian d foy explains:
You can then catch that error and skip those invalid strings.
Or if you know your code is mostly UTF-8 with a few invalid sequences here and there, you can use:
which uses the default mode of
FB_DEFAULT, replacing invalid characters with U+FFFD, the Unicode REPLACEMENT CHARACTER (diamond with question mark in it).You can then pass the string directly to your database driver in most cases. Some drivers may require you to re-encode the string back to byte form first:
There are also regexes online that you can use to check for valid UTF-8 sequences before calling
decode(check other Stack Overflow answers). If you use those regexes, you don’t need to do any encoding or decoding.Finally, please use
UTF-8rather thanutf8in your calls todecode. The latter is more lax and allows some invalid UTF-8 sequences (such as sequences outside the Unicode range) to be allowed through.