30

I have a Unicode string and don't know what its encoding is. When this string is read by a Perl program, is there a default encoding that Perl will use? If so, how can I find out what it is?

I am trying to get rid of non-ASCII characters from the input. I found this on some forum that will do it:

my $line = encode('ascii', normalize('KD', $myutf), sub {$_[0] = ''});

How will the above work when no input encoding is specified? Should it be specified like the following?

my $line = encode('ascii', normalize('KD', decode($myutf, 'input-encoding'), sub {$_[0] = ''});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Maulin
  • 1,901
  • 4
  • 19
  • 25
  • 1
    Correct me if I'm wrong, but I thought the whole point of Unicode is that it doesn't have an "encoding" - everything is there. – Paul Tomblin Dec 28 '09 at 17:55
  • 3
    You don't have a Unicode string. You have a file full of bytes that represent a Unicode string in some encoding. If you don't know the encoding of the file, then check for a byte-order-marker at beginning of the file. If there is no BOM, you're out of luck. – Jonathan Feinberg Dec 28 '09 at 17:57
  • 1
    It looks like you're asking at least two separate question (1st and 2nd paragraphs). – Amnon Dec 28 '09 at 17:57
  • 3
    @Paul: Unicode defines a character set. An encoding of that character set represents it on disk. For instance, UTF-8 is an encoding. – brian d foy Dec 29 '09 at 08:42

4 Answers4

37

To find out in which encoding something unknown uses, you just have to try and look. The modules Encode::Detect and Encode::Guess automate that. (If you have trouble compiling Encode::Detect, try its fork Encode::Detective instead.)

use Encode::Detect::Detector;
my $unknown = "\x{54}\x{68}\x{69}\x{73}\x{20}\x{79}\x{65}\x{61}\x{72}\x{20}".
              "\x{49}\x{20}\x{77}\x{65}\x{6e}\x{74}\x{20}\x{74}\x{6f}\x{20}".
              "\x{b1}\x{b1}\x{be}\x{a9}\x{20}\x{50}\x{65}\x{72}\x{6c}\x{20}".
              "\x{77}\x{6f}\x{72}\x{6b}\x{73}\x{68}\x{6f}\x{70}\x{2e}";
my $encoding_name = Encode::Detect::Detector::detect($unknown);
print $encoding_name; # gb18030

use Encode;
my $string = decode($encoding_name, $unknown);

I find encode 'ascii' is a lame solution for getting rid of non-ASCII characters. Everything will be substituted with questions marks; this is too lossy to be useful.

# Bad example; don't do this.
use utf8;
use Encode;
my $string = 'This year I went to 北京 Perl workshop.';
print encode('ascii', $string); # This year I went to ?? Perl workshop.

If you want readable ASCII text, I recommend Text::Unidecode instead. This, too, is a lossy encoding, but not as terrible as plain encode above.

use utf8;
use Text::Unidecode;
my $string = 'This year I went to 北京 Perl workshop.';
print unidecode($string); # This year I went to Bei Jing  Perl workshop.

However, avoid those lossy encodings if you can help it. In case you want to reverse the operation later, pick either one of PERLQQ or XMLCREF.

use utf8;
use Encode qw(encode PERLQQ XMLCREF);
my $string = 'This year I went to 北京 Perl workshop.';
print encode('ascii', $string, PERLQQ);  # This year I went to \x{5317}\x{4eac} Perl workshop.
print encode('ascii', $string, XMLCREF); # This year I went to 北京 Perl workshop.
daxim
  • 39,270
  • 4
  • 65
  • 132
  • The input I receive always uses the Latin character-set. The normalize function I use would then convert "Café" to "Cafe". This does not work in all cases though. Given this, would you still prefer to use the PERLQQ or XMLCREF method? – Maulin Dec 29 '09 at 14:52
  • It does not matter what I prefer – it's your code and responsibility after all, and only you know all the circumstances. If indeed you are happy with Café → Cafe, then replace your custom function with `Text::Unidecode`. That does work in all cases. – daxim Dec 29 '09 at 18:07
4

The Encode module has a way that you can try to do this. You decode the raw octets with what you think the encoding is. If the octets don't represent a valid encoding, it blows up and you catch it with an eval. Otherwise, you get back a properly encoded string. For example:

 use Encode;

 my $a_with_ring =
   eval { decode( 'UTF-8', "\x6b\xc5", Encode::FB_CROAK ) }
     or die "Could not decode string: $@";

This has the drawback that the same octet sequence can be valid in multiple encodings

I have more to say about this in the upcoming Effective Perl Programming, 2nd Edition, which has an entire chapter on dealing with Unicode. I think my publisher would get mad if I posted the whole thing though. :)

You might also want to see Juerd's Unicode Advice, as well as some of the Unicode docs that come with Perl.

Cosmicnet
  • 389
  • 4
  • 14
brian d foy
  • 129,424
  • 31
  • 207
  • 592
0

I like mscha's solution here, but simplified using Perl's defined-or operator (//):

sub slurp($file)
 local $/;
 open(my $fh, '<:raw', $file) or return undef();
 my $raw = <$fh>;
 close($fh);

 # return the first successful decoding result
 return
  eval { Encode::decode('utf-8', $raw, Encode::FB_CROAK); } //        # Try UTF-8
  eval { Encode::decode('windows-1252', $raw, Encode::FB_CROAK); } // # Try windows-1252 (a superset of iso-8859-1 and ascii)
  $raw;                                                               # Give up and return the raw bytes
}

The first successful decoding is returned. Plain ASCII content succeeds in the first decoding.

If you are working directly with string variables instead of reading in files, you can use just the successive-eval expression.

chrispitude
  • 121
  • 2
  • 6
-4

You can use the following code also, to encrypt and decrypt the code

sub ENCRYPT_DECRYPT() {
    my $Str_Message=$_[0];
    my  $Len_Str_Message=length($Str_Message);

    my  $Str_Encrypted_Message="";
    for (my $Position = 0;$Position<$Len_Str_Message;$Position++){
        my  $Key_To_Use = (($Len_Str_Message+$Position)+1);
            $Key_To_Use =(255+$Key_To_Use) % 255;
        my  $Byte_To_Be_Encrypted = substr($Str_Message, $Position, 1);
        my  $Ascii_Num_Byte_To_Encrypt = ord($Byte_To_Be_Encrypted);
        my  $Xored_Byte = $Ascii_Num_Byte_To_Encrypt ^ $Key_To_Use;
            my  $Encrypted_Byte = chr($Xored_Byte);
        $Str_Encrypted_Message .= $Encrypted_Byte;

    }
    return $Str_Encrypted_Message;
}

 my $var=&ENCRYPT_DECRYPT("hai");
 print &ENCRYPT_DECRYPT($var);
muruga
  • 2,092
  • 2
  • 20
  • 28