Melly has asked for the wisdom of the Perl Monks concerning the following question:

Hi,

I'm running the following script against some CSV files prior to doing anything else. It basically checks for non-ascii, and either makes the specified conversion, or shows the value that it didn't have a spec for.

However, others will need to make use of the script, which means that adding new regexes to it would be inconvenient. We use SVN, but that's still a kludge.

Can anyone see a good way to load the RegExes from an eternal file?

Also, and unrelated, do I need to use binary? It seems to be advised, but I thought that was just line-endings, and it means that I have to do my own translation, which seems a bit redundant. And can I check? Do I need "use utf8" as I'm just searching for octal sequences? (iirc yes).

use strict; use utf8; use File::Basename; my @files = glob($ARGV[0]); my $outdir = $ARGV[1]; my $debug = $ARGV[2]; die "No output directory given\n" unless -d $outdir; $outdir =~ s/\\/\//g; # backslash to forward $outdir =~ s/([^\/])$/$1\//; # add final slash if missing foreach my $file(@files){ my $outfile = $outdir . '/' . basename($file); open(CSV, '<', $file)||die "Cannot open $file for read:$!\n"; binmode CSV; open(OUT, '>', $outfile)||die "Cannot open $outfile for write:$!\n"; while (my $line = <CSV>){ $line =~ s/\x0D\x0A/\n/g; # binary, so we're still stuck with \r\n + dos endings possibly - why are we using binary? if($line =~ /[^[:ascii:]]/){ print "Before: $line\n" if $debug; # translations from octal sequence to ascii char $line =~ s/\302\267/./g; # odd utf 'floating' point to a +scii . $line =~ s/\342\200\230/'/g; # left single curly quote to as +cii ' $line =~ s/\342\200\231/'/g; # right single curly quote to a +scii ' $line =~ s/\342\200\223/-/g; # em-dash to ascii - $line =~ s/\303\257/i/g; # double-dot i to ascii i $line =~ s/\302\243/GBP/g; # pound sign to GBP $line =~ s/\342\200\246/.../g; # elipsis to ascii ... $line =~ s/\302\256/(a)/g; # @ to (a) $line =~ s/\303\250/e/g; # grave e to e $line =~ s/\303\251/e/g; # acute e to e $line =~ s/\342\211\244/\>\=/g; # utf >= to ascii >= $line =~ s/\342\211\245/\<\=/g; # utf <= to ascii <= $line =~ s/\303\264/o/g; # circumflex o (?!?) to ascii o $line =~ s/\302\240/\s/g; # nbsp to sp $line =~ s/\302\263/\^3/g; # superscript 3 to ^3 $line =~ s/\302\262/\^2/g; # superscript 2 to ^2 $line =~ s/\302\260/ degrees/g; # degrees symbol to word ' degr +ees' $line =~ s/\342\200\235/""/g; # right double curly quote to a +scii " (escaped for csv) $line =~ s/\342\200\234/""/g; # left double curly quote to as +cii " (escaped for csv) $line =~ s/\302\275/1\/2/g; # utf 1/2 to ascii plain 1/2 if($line =~ /[^[:ascii:]]/){ $line =~ s/([^[:ascii:]])/'[' . (ord $1) . '\/' . (sprintf("0x +%X", (ord $1))) . '\/' . (sprintf("%o", (ord $1))) . ']'/ge; print "Unhandled sequence: $line\n"; } print "After: $line\n" if $debug; } print OUT "$line"; } }
map{$a=1-$_/10;map{$d=$a;$e=$b=$_/20-2;map{($d,$e)=(2*$d*$e+$a,$e**2 -$d**2+$b);$c=$d**2+$e**2>4?$d=8:_}1..50;print$c}0..59;print$/}0..20
Tom Melly, pm (at) cursingmaggot (stop) co (stop) uk

Replies are listed 'Best First'.
Re: Read RegEx from file
by 1nickt (Canon) on Jan 14, 2016 at 16:27 UTC

    Hi Melly,

    Just to touch on one point in your OP:

    From the docs for utf8:

    "The use utf8 pragma tells the Perl parser to allow UTF-8 in the program text in the current lexical scope (...)
    Do not use this pragma for anything else than telling Perl that your script is written in UTF-8."

    (Emphasis in original)


    The way forward always starts with a minimal test.

      So I'd only need utf8 if I was specifying my search expressions in utf-8 rather than using the octal notation? (or, for some reason, wanted to use utf in a variable name).

      Basically, I read your advice as "you can take it out - it's not needed for reading from a utf8 file" - is that right?

      map{$a=1-$_/10;map{$d=$a;$e=$b=$_/20-2;map{($d,$e)=(2*$d*$e+$a,$e**2 -$d**2+$b);$c=$d**2+$e**2>4?$d=8:_}1..50;print$c}0..59;print$/}0..20
      Tom Melly, pm (at) cursingmaggot (stop) co (stop) uk

        That's right. You only need (and should only use) use utf8; if the source code of your script is in UTF-8, e.g. variable names, text within the script file, etc.

        For reading in a file that is UTF-8, use an IO layer. See binmode, the docs for which say:

        To mark FILEHANDLE as UTF-8, use :utf8 or :encoding(UTF-8) . :utf8 jus +t marks the data as UTF-8 without further checking, while :encoding(U +TF-8) checks the data for actually being valid UTF-8. More details ca +n be found in PerlIO::encoding.

        The way forward always starts with a minimal test.
Re: Read RegEx from file
by AnomalousMonk (Archbishop) on Jan 14, 2016 at 23:12 UTC
    Can anyone see a good way to load the RegExes from an eternal file?

    There is probably a module for this somewhere, but if not, something along the lines of the following may serve. Your search patterns all seem to be plain octal sequences of various lengths and the replacement strings character string literals, so that simplifies things. I assume from your OP that you're confident about reading raw string data from a file and massaging it appropriately, so:

    c:\@Work\Perl>perl -wMstrict -le "use Data::Dump qw(dd); ;; my @filelines = ( '\102\105\105 B', '\104\125\102\131\101 W', '\130 EKS', '\132 ZEE', ); ;; my %search_terms = map split, @filelines; ;; my %xlate = map { eval(qq{ qq{$_} }) => $search_terms{$_} } keys %search_terms ; dd \%xlate; ;; my ($search) = map qr{ $_ }xms, join ' | ', reverse sort keys %search_terms ; print $search; ;; my $s = 'the BEEroDUBYAn foX jumped the laZy dog'; print qq{'$s'}; ;; $s =~ s{ ($search) }{$xlate{ $1 }}xmsg; print qq{'$s'}; " { BEE => "B", DUBYA => "W", X => "EKS", Z => "ZEE" } (?^msx: \132 | \130 | \104\125\102\131\101 | \102\105\105 ) 'the BEEroDUBYAn foX jumped the laZy dog' 'the BroWn foEKS jumped the laZEEy dog'

    Some other more or less random thoughts:

    •     $line =~ s/\302\240/\s/g;        # nbsp to sp
      If this is supposed to translate something to a space character, it doesn't:  \s has no special meaning in a double-quoted string:
      c:\@Work\Perl>perl -wMstrict -le "my $s = '>nbsp< hi nbsp there'; print qq{'$s'}; ;; $s =~ s{ nbsp }{\s}xmsg; print qq{'$s'}; " Unrecognized escape \s passed through at -e line 1. '>nbsp< hi nbsp there' '>s< hi s there'
      Perl could have warned you about this had you had warnings enabled.
    •     $line =~ s/\342\211\244/\>\=/g;  # utf >= to ascii >=
      This is not critical, but in general, escaped characters like  \>\= in interpolated strings have no special meaning and should, IMHO, always produce warnings.
    •     $line =~ s/\302\256/(a)/g;       # @ to (a)
      Just out of curiosity, why not translate to ASCII '@'?
    • if($line =~ /[^[:ascii:]]/){ $line =~ s/([^[:ascii:]])/ ... lotsa subexpressions ... /ge; print "Unhandled sequence: $line\n"; }
      This is only cosmetic, but this could be much simpler and IMHO clearer in consequence. The  s/// operator does not do substitutions if the pattern is not matched, and can be used as | to control a conditional because it returns the number of matches/substitutions in scalar/boolean context. One can also capitalize on obscure sprintf format specifiers:
      c:\@Work\Perl>perl -wMstrict -e "my $s = 'aXbYcZd'; print qq{'$s' \n}; ;; print qq{unhandled sequence: '$s' \n} if $s =~ s{ ([XYZ]) }{ sprintf '[%d/%1$#X/%1$#o]', ord $1 }xmsge; " 'aXbYcZd' unhandled sequence: 'a[88/0X58/0130]b[89/0X59/0131]c[90/0X5A/0132]d'

    Update:
        $line =~ s/\302\240/\s/g;        # nbsp to sp
    So, I guess this raises the question of just how you get a literal space from a space-delimited file to use as a replacement string, anyway? One way would be to use a non-space delimiter, but this may just shift the problem to another boundary. Another approach would be to specify all such characters in their octal equivalent,  \040 and so on. This sets us upon the the long and winding road of  s///ee and template processing (pursue the links from Re: Evaluating $1 construct in literal replacement expression). In any event, here's one way:

    c:\@Work\Perl>perl -wMstrict -le "use Data::Dump qw(dd); ;; my @filelines = ( '\102\105\105 B', 'nbsp \040', '\130 EKS', '\132 ZEE', ); ;; my %search_terms = map split, @filelines; ;; my %xlate = map { eval(qq{ qq{$_} }) => $search_terms{$_} } keys %search_terms ; dd \%xlate; ;; my ($search) = map qr{ $_ }xms, join ' | ', reverse sort keys %search_terms ; print $search; ;; my $s = 'the BEErownbspfoX >nbsp< the laZy dog'; print qq{'$s'}; ;; $s =~ s{ ($search) }{ qq{ qq{$xlate{$1}} } }xmsgee; print qq{'$s'}; " { BEE => "B", nbsp => "\\040", X => "EKS", Z => "ZEE" } (?^msx: nbsp | \132 | \130 | \102\105\105 ) 'the BEErownbspfoX >nbsp< the laZy dog' 'the Brow foEKS > < the laZEEy dog'


    Give a man a fish:  <%-{-{-{-<

      Many thanks for that - most useful.

      In answer to some specific questions:

      Silly me regarding \s - these are nbsp characters embedded within a quoted string, so replacing with " " works fine.

      Re @ - I'm an idiot. I did a search for the hex sequence, saw it was a @, thought to myself "oh - I thought @ was ascii, oh well", and did the replacement (why does utf bother to encode a character that exists in ascii?).

      UPDATE - utf does have an alternative for @, but I've just realised that that particular sequence is a registration mark - in this case, a doctor is using it as a short-hand for 'right' in a word doc, and then pasting into a db.

      And, yes, my escaping habits are terrible and I am suitably ashamed.

      map{$a=1-$_/10;map{$d=$a;$e=$b=$_/20-2;map{($d,$e)=(2*$d*$e+$a,$e**2 -$d**2+$b);$c=$d**2+$e**2>4?$d=8:_}1..50;print$c}0..59;print$/}0..20
      Tom Melly, pm (at) cursingmaggot (stop) co (stop) uk
      So, I guess this raises the question of just how you get a literal space from a space-delimited file to use as a replacement string, anyway? One way would be to use a non-space delimiter, but this may just shift the problem to another boundary.
      That is a well known problem and it has been solved over and over again. Probably the easiest way to do it is by using a "CSV"-style file and Text::CSV knows how to do this perfectly.

      CountZero

      A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

      My blog: Imperial Deltronics