Beefy Boxes and Bandwidth Generously Provided by pair Networks RobOMonk
go ahead... be a heretic
 
PerlMonks  

Re^3: Unicode substitution regex conundrum

by moritz (Cardinal)
on Oct 16, 2007 at 14:11 UTC ( #645182=note: print w/ replies, xml ) Need Help??


in reply to Re^2: Unicode substitution regex conundrum
in thread Unicode substitution regex conundrum

If you don't know the encoding of data, how shall perl know it?

When you don't set CGIs charset parameter, it will return a byte string, that you have to convert to perl's internal format... but I wrote that already.

You can try to use Encode::Guess if you have a few possible charsets that aren't too similar and your input data is long enough.

If this doesn't hold true you have to take care that your input will be in a known encoding, for example in HTML forms you can set the accept-charset attribute to utf8 only.

And when you want unicode semantics in regex matches, check with encode::is_utf8($string) that it is indeed in perl's internal format.


Comment on Re^3: Unicode substitution regex conundrum
Download Code
Re^4: Unicode substitution regex conundrum
by Juerd (Abbot) on Oct 16, 2007 at 19:11 UTC

    "the" internal format for Unicode strings is actually two different formats: ISO-8859-1 and UTF8. As an optimization, Perl may use ISO-8859-1 even though the original source was UTF-8.

    Encode::_is_utf8 (not encode::is_utf8) will return true if the string is internally encoded as UTF8, and can return false even though you properly decoded.

    Recommendation: do not look at the UTF8 flag. It is next to useless, except for internals debugging and performance tweaking.

    Pretend that the UTF8 flag does not exist.

    Do not use Encode::_is_utf8 (it is prefixed with an underscore for a reason: you should not have to use this in normal code).

    Really, Perl's Unicode strings may be encoded as *ANYTHING* internally. Don't look at the internal buffer, unless you really want to mess with the internals. You do not need to know the internals for simple text processing, as is the case in the OP's problem.

      I meant utf8's is_utf8 originally.

      And what is your suggestion how to check if a string is already decoded into perl's internal format?

      Sadly enough many modules don't document how and if at all they handle encoding issues, so sometimes I do need a method to check that.

        And what is your suggestion how to check if a string is already decoded into perl's internal format?

        Every string is. And that is why your "convert to Perl's internal format" is a bit tricky. It's not incorrect, but it converts both FROM and TO this format. Even binary strings are in this format, or well, one of these formats.

        Perl makes no distinction internally between binary strings and latin1-encoded text strings. None whatsoever. As such, it is impossible for perl to tell you whether a certain string is a text string or a binary string. If the UTF8 flag is true, it certainly is a text string, but if it is false, it can be either text or binary.

        Every string starts out (again, internally) as a binary/latin1 string. The buffer is upgraded when that is needed or convenient. The internal representation of a text string may change, but the string that you, the Perl programmer, work with, is consistent: your chr 0x80 stays 128, regardless of the internal encoding for it.

        You do have to be extra careful not to mix binary with text, because that either makes perl interpret the binary as latin1-encoded text, or the text buffer (which can be latin1 or utf8) as binary.

        Binary string can be safely used without explicitly mentioning that they are binary. This is a feature that ensures backwards compatibility, and a deliberate hole in the abstraction layer, that shouldn't hurt anyone except those who fail to separate binary from text :)

        I recommend saying "decode the binary input to a text string" instead of "convert the string to Perl's internal format", because that would suggest that before conversion it would be something other than a Perl string. The distinction is not "internal format" versus "external format", it is also not "latin1" versus "utf8", it is not "utf8 flagged" versus "not utf8 flagged", and it is certainly not "not flagged" versus "unicode".

        The distinction is "binary string" versus "text string" and the classification of a string should live in the mind of the programmer, because it doesn't live in perl's internals. Binary and text are inherently different, even though they look much alike.

        Compare it to packed numbers versus "numeric Perl values", because that's very much the same thing: the internal representation of a number is scaled up and down based on whatever happens with a number, while pack provides a way to get a certain encoding of the number, resulting in a binary string. Similarly, the internal representation of a text string is scaled up and down based on whatever happens with the string, while encode provides a way to get a certain encoding of the text string, resulting in a binary string. The problem with the two string kinds is that they share a single data type.

        Juerd # { site => 'juerd.nl', do_not_use => 'spamtrap', perl6_server => 'feather' }

        Sadly enough many modules don't document how and if at all they handle encoding issues, so sometimes I do need a method to check that.

        Maybe http://juerd.nl/perladvice can be of a little help.

Re^4: Unicode substitution regex conundrum
by Polyglot (Monk) on Oct 17, 2007 at 03:35 UTC
    Well, I'm stumped. The program will match the spaces properly when doing a split// but not when doing a s///. I have now set the attribute on the HTML form to UTF8. I have inserted the code posted earlier, and still nothing. So, to demonstrate the exact conundrum I am up against, I have reduced my code to just the barest essentials for testing this UTF8 regex.

    Please feel free to try this script on your own server to see if you can get it to work properly on Chinese fonts. I have included a sample Chinese phrase in the script which you should be able to copy and paste into it for testing purposes. Compare it with an English search, and you'll see why I'm frustrated!

    #!/usr/bin/perl -wT -CE use Encode; use Encode qw(_utf8_on); use Encode qw(encode decode); ##### PARSE THE FORM INPUT if ($ENV{CONTENT_LENGTH}) { read(STDIN, $buffer, $ENV{CONTENT_LENGTH}); @pairs = split(/&/,$buffer); } else { $buffer = $ENV{QUERY_STRING}; @pairs = split(/\+/,$buffer); } foreach $pair (@pairs) { ($name, $value) = split(/=/,$pair); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; $input{$name} = $value; } $terms=$input{terms}; ##### START TESTING PHASE print "Content-type: text/html\n\n"; print "TERMS: $terms"; ##### TRY A SPLIT ($a, $b, $c, $d, $e, $f) = split/\p{IsSpace}/, $terms; print "<p>A:$a:<p>B:$b:<p>C:$c:<p>D:$d:<p>E:$e:<p>F:$f:\n"; ##### NOW TRY A SUBSTITUTION $word = qr/\b(?!(?:AND|OR|XOR|NOT)\b)\w+/i; $terms =~ s/($word)\p{IsSpace}*($word)/$1 AND $2/g for 1..2; print "<p>Terms:$terms\n"; ##### PRINT THE WEBPAGE print <<HTML; <html lang=utf8> <head> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf8"> <title>SEARCH</title> </head><body> <h1 align="center">Search</h1> <form name="ff" method="POST" accept-encoding="UTF-8" accept-charset="utf-8" action="$0"> Search terms: <input type="text" size="40" name="terms" value="$terms"></input> <p>An example Chinese phrase: &#32102;&#32842; &#22825;&#23460;&#25152 +; &#26377;&#25104;&#21729; <input type="submit" name="submit" value="Submit"></input> </form></body></html> HTML
      Hi, I may be too late, seeing your message has been here since nearly two months, but it could still be of use to you or someone else.

      I may be wrong, but it seems to me like the problem does not reside with the whitespaces, but with the definition of word in Perl : \w+ does not match chinese characters.

      On my system (with unicode locale and chinese readable in the console) :
      $ perl -le 'print "ok" if ("&#25105;&#36208;" =~ m/\w+/)' $ perl -le 'print "ok" if ("hi" =~ m/\w+/)' ok
      (Chinese chars were jumbled, I didn't put the codes in the one-liner)

      Furthermore, I played a bit with your code, and when I replaced
      $terms =~ s/($word)\p{IsSpace}*($word)/$1 AND $2/g for 1..2;
      with
      $terms =~ s/(\p{IsSpace}/ AND /g;
      it did the job I expected of it.

      The quickest workaround I see at the moment would be to declare $word using CJK character ranges instead of \w.

      Hope I could be of help.

      Lu.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://645182]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (7)
As of 2014-04-20 14:58 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (485 votes), past polls