http://www.perlmonks.org?node_id=645172


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

Thank you for the helpful responses. I learned several new things. However, I am still unable to get my program functioning correctly.

For starters, I am not opening a file. I am receiving the utf8 input from an HTML form, and, quite frankly, I have no way of knowing whether user input will be received in the Perl script as utf8, unicode, big5, gb2312, etc., although I am trying to work solely with utf8. The database being searched is in utf8, but Perl is doing next to nothing with it once received with the DBI commands, other than outputting it back to HTML.

On your advice, I tried learning more about utf8, and have tried using the -COE command in the shebang line (this rendered the output unreadable), and I tried using the Encode qw(encode decode), to no avail.

Here are my current uses:

use CGI; use CGI::Carp qw(fatalsToBrowser); use strict; use DBI; use Encode; use Encode qw(_utf8_on); use Encode::HanConvert; #Module for dealing with CJK conversions use Encode qw(encode decode); require Encode::CN; require Encode::TW;
So I am still left wondering how to make perl see the string as utf8. Am I missing something? Is Perl 5.8.7 recent enough? How can I flag utf8 on input from a form rather than a file?

The real puzzle, to me, is that split// seems to work, but the s/// won't work. Unfortunately, I have no way of knowing how many terms the user will be entering, so using split would require some significant recoding. Thank you!

Replies are listed 'Best First'.
Re^3: Unicode substitution regex conundrum
by moritz (Cardinal) on Oct 16, 2007 at 14:11 UTC
    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.

      "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.

      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.