Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Re: Unicode substitution regex conundrum

by Juerd (Abbot)
on Oct 16, 2007 at 10:55 UTC ( #645149=note: print w/ replies, xml ) Need Help??


in reply to Unicode substitution regex conundrum

"use utf8;" is needed if your source code is actually in UTF8 encoding. It does not affect regex matches.

Perl does not have UTF8 semantics, but instead Unicode semantics. The difference is that you work on *encodingless* strings in Perl, and that you use *normal* operators instead of separate ones. The important things are done internally.

Please read the Perl Unicode Tutorial and the Perl Unicode FAQ.

The following ought to suffice:

# untested! use Encode qw(decode); use Unicode::Semantics qw(up); up($line = decode 'UTF-8', $line); my $word = qr/\b(?!(?:AND|OR|XOR|NOT)\b)\w+/i; $line =~ s/($word)\s*($word)/$1 AND $2/g for 1..2;
Unicode::Semantics works around a bug that causes the second half of latin1 to be ignored under certain circumstances.

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


Comment on Re: Unicode substitution regex conundrum
Download Code
Re^2: Unicode substitution regex conundrum
by Polyglot (Monk) on Oct 16, 2007 at 13:55 UTC
    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!

      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.

        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
Re^2: Unicode substitution regex conundrum
by Polyglot (Monk) on Mar 04, 2008 at 06:14 UTC
    This is late, but I do have the script working well now, and I wanted to say a big thank you to those of you who offered your support. Juerd was probably closest to the solution that seemed to fit best in my situation.

    For those who may be seeking the same wisdom, I would like to post the full solution in my case.

    My entire "use" section:

    use CGI; use CGI::Carp qw(fatalsToBrowser); use strict; use DBI; use Encode; use Encode::HanConvert; #Module for dealing with CJK conversions use Encode qw(encode decode); use POSIX qw(locale_h); require Encode::CN; require Encode::TW; require 5.004;
    For incoming form values:
    $name = decode("utf-8", $name); $value = decode("utf-8", $value);
    For incoming values from the database:
    my $quest = $dbh->prepare($statement, { RaiseError => 1 }) or die "Cannot prepare statement! $DBI::errstr\n"; while(@row = $quest->fetchrow_array()) { $c1=shift @row; $c1=decode("utf-8", $c1); ... }
    And finally, an example of the regex which now functions on multiple languages (UTF8):
    $line =~ s%(?:\p{IsSpace}*) #Match zero or more spaces (\bNOT)? #Match zero or one "NOT" operator(s) (\(*) #Match zero or more left parentheses (\p{IsSpace}*|\s*|\b|^) #Match zero+ spaces or a word boundary (?!\") #Ensure this doesn't appear beforehand ((?:\p{IsWord}|\w|`| #Match zero+ words (?:\&\p{IsAlnum}*\;)*)* #Include HTML special chars, e.g. &aacute; (?:\.\{\d+\})* #Include zero+ MySQL-style wildcard '?'s (?:\[\.[^\.\]]*\.\])* #Include zero+ MySQL REGEXP chars (?:\[\:[^\:\]]*\:\])* #Include zero+ MySQL REGEXP special chars (?:\[[^\]]*\])* #Incl. zero+ MySQL REGEXP special patterns (?:\*(?!\"))* #Include zero+ stand-alone asterisks (?:\%(?!\"))* ) #Include zero+ stand-alone percent signs (?:\s*|\p{IsSpace}*) #Match zero+ spaces (\)*) #Match zero+ right parentheses (?:\p{IsSpace}|\s)* #Match zero+ spaces (["()]*) #Match zero+ double quotes or parentheses (?:\p{IsSpace}*) #Match zero+ spaces ( #(begin group) (?:NOT|OR|AND|XOR)* #Match zero+ operator words (?:\p{IsSpace}+|\(+|\p{IsZ}|\Z) #Then one+ spaces OR one+ ")" #OR end-of-string ) #(end group) #AND SUBSTITUTE THE ABOVE WITH THE BELOW %$2$3$table\.$columnName`$1`$like`"$l$wb$4$we$l"$5$6 $7 %xig;
    Again, thank you very much! And thank you to Moritz who prodded me to learn how to code the regex substitution on multiple lines, with comments for readability. Blessings!

    ~Polyglot~

      That "use 5.004" is useless; Encode already requires 5.8. I recommend using "use 5.8.3;" or "use 5.008003";

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (11)
As of 2014-12-27 17:35 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (177 votes), past polls