Convert a UTF8 string to ASCII in Perl

34,289

Solution 1

The answer depends on how you want to use the title. There are 3 basic ways to go:

  • Bytes that represent a UTF-8 encoded string.

This is the format that should be used if you want to store the UTF-8 encoded string outside your application, be it on disk or sending it over the network or anything outside the scope of your program.

  • A string of Unicode characters.

The concept of characters is internal to Perl. When you perform Encode::decode_utf8, then a bunch of bytes is attempted to be converted to a string of characters, as seen by Perl. The Perl VM (and the programmer writing Perl code) cannot externalize that concept except through decoding UTF-8 bytes on input and encoding them to UTF-8 bytes on output. For example, your program receives two bytes as input that you know they represent UTF-8 encoded character(s), let's say 0xC3 0xB6. In that case decode_utf8 returns a representation that instead of two bytes, sees one character: ö.

You can then proceed to manipulate that string in Perl. To illustrate the difference further, consider the following code:

my $bytes = "\xC3\xB6";
say length($bytes); # prints "2"
my $string = decode_utf8($bytes);
say length($string); # prints "1"
  • The special case of ASCII, a subset of UTF-8.

    ASCII is a very small subset of Unicode, where characters in that range are represented by a single byte. Converting Unicode into ASCII is an inherently lossy operation, as most of the Unicode characters are not ASCII characters. You're either forced to drop every character in your string which is not in ASCII or try to map from a Unicode character to their closest ASCII equivalents (which isn't possible in the vast majority of cases), when trying to coerce a Unicode string to ASCII.

Since you have wide character warnings, it means that you're trying to manipulate (possibly output) Unicode characters that cannot be represented as ASCII or ISO-8859-1.

If you do not need to manipulate the title from your XML document as a string, I'd suggest you leave it as UTF-8 bytes (I'd mention that you should be careful not to mix bytes and characters in strings). If you do need to manipulate it, then decode, manipulate, and on output encode it in UTF-8.

For further reading, please use perldoc to study perlunitut, perlunifaq, perlunicode, perluniintro, and Encode.

Solution 2

Although this is an old question, I just spent several hours (!) trying to do more or less the same thing! That is: read data from a UTF-8 XML file, and convert that data into the Windows-1252 codepage (I could also have used Latin1, ISO-8859-1 etc.) in order to be able to create filenames with accented letters.

After much experimentation, and even more searching, I finally managed to get the conversion working. The "trick" is to use Encode::encode instead of Encode::decode.

For example, given the code in the original question, the correct (or at least one :-) way to convert from UTF-8 would be:

my $title = Encode::encode("Windows-1252", $item->{title});

or

my $title = Encode::encode("ISO-8859-1", $item->{title});

or

my $title = Encode::encode("<your-favourite-codepage-here>", $item->{title});

I hope this helps others having similar problems!

Solution 3

You can use the following line to simply get rid of the warning. This assumes that you want to use UTF8, which shouldn't normally be a problem.

binmode(STDOUT, ":encoding(utf8)");

Share:
34,289
Mark C
Author by

Mark C

Updated on August 09, 2022

Comments

  • Mark C
    Mark C almost 2 years

    I've tried everything Google and StackOverflow have recommended (that I could find) including using Encode. My code works but it just uses UTF8 and I get the wide character warnings. I know how to work around those warnings but I'm not using UTF8 for anything else so I'd like to just convert it and not have to adapt the rest of my code to deal with it. Here's my code:

    my $xml = XMLin($content);
    # Populate the @titles array with each item title.
    my @titles;
    for my $item (@{$xml->{channel}->{item}}) {
        my $title = Encode::decode_utf8($item->{title});
        #my $title = $item->{title};
        #utf8::downgrade($title, 1);
        Encode::from_to($title, 'utf8', 'iso-8859-1');
        push @titles, $title;
    }
    return @titles;
    

    Commented out you can see some of the other things I've tried. I'm well aware that I don't know what I'm doing here. I just want to end up with a plain old ASCII string though. Any ideas would be greatly appreciated. Thanks.