File Coverage

blib/lib/Lingua/Jspell.pm
Criterion Covered Total %
statement 49 472 10.3
branch 7 274 2.5
condition 0 72 0.0
subroutine 13 45 28.8
pod 20 20 100.0
total 89 883 10.0


line stmt bran cond sub pod time code
1             package Lingua::Jspell;
2              
3 2     2   86559 use warnings;
  2         5  
  2         69  
4 2     2   16 use strict;
  2         4  
  2         38  
5              
6 2     2   46 use 5.008001;
  2         8  
7              
8 2     2   1300 use POSIX qw(locale_h);
  2         13396  
  2         10  
9             setlocale(LC_CTYPE, "pt_PT");
10 2     2   4352 use locale;
  2         1249  
  2         12  
11              
12 2     2   87 use base 'Exporter';
  2         5  
  2         288  
13             our @EXPORT_OK = (qw.onethat verif nlgrep setstopwords
14             onethatverif any2str hash2str isguess.);
15              
16             our %EXPORT_TAGS = (basic => [qw.onethat verif onethatverif
17             any2str hash2str isguess.],
18             greps => [qw.nlgrep setstopwords.]);
19             # use Data::Dumper;
20 2     2   1058 use File::Spec::Functions;
  2         1726  
  2         167  
21 2     2   1007 use Lingua::Jspell::ConfigData;
  2         4  
  2         75  
22 2     2   931 use Lingua::Jspell::EAGLES;
  2         6  
  2         63  
23 2     2   1164 use IPC::Open3;
  2         8452  
  2         119  
24 2     2   1134 use YAML qw/LoadFile/;
  2         17128  
  2         109  
25 2     2   1145 use Data::Compare;
  2         30429  
  2         14  
26              
27             =head1 NAME
28              
29             =encoding utf8
30              
31             Lingua::Jspell - Perl interface to the Jspell morphological analyser.
32              
33             =cut
34              
35             our $VERSION = '1.96';
36             our $JSPELL;
37             our $JSPELLLIB;
38             our $MODE = { nm => "af", flags => 0 };
39             our $DELIM = '===';
40             our %STOP =();
41              
42             BEGIN {
43 2     2   4953 delete @ENV{qw(IFS CD PATH ENV BASH_ENV)}; # Make %ENV safer
44              
45 2         6 my $EXE = "";
46 2 50       10 if ($^O eq "MSWin32") {
47 0         0 $ENV{PATH} = "blib\\usrlib";
48 0         0 $EXE=".exe" ;
49              
50 0         0 my $dllpath = Lingua::Jspell::ConfigData->config("libdir");
51 0         0 $ENV{PATH} = join(";", $dllpath, $ENV{PATH});
52             }
53              
54 2         4 local $_;
55              
56 2         22 $JSPELL = catfile("blib","bin","jspell$EXE");
57 2 50       57 $JSPELL = Lingua::Jspell::ConfigData->config("jspell") unless -x $JSPELL;
58              
59 2 50       43 die "jspell binary cannot be found!\n" unless -x $JSPELL;
60              
61 2         20 local $.;
62 2 50       5565 open X, "$JSPELL -vv|" or die "Can't execute $JSPELL";
63 2         1552 while () {
64 118 100       441 if (/LIBDIR = "([^"]+)"/) {
65 2         61 $JSPELLLIB = $1;
66             }
67             }
68 2         96 close X;
69 2 50       15419 die "Can't find out jspell lib dir" unless $JSPELLLIB;
70             }
71              
72             =head1 SYNOPSIS
73              
74             use Lingua::Jspell;
75              
76             my $dict = Lingua::Jspell->new( "dict_name");
77             my $dict = Lingua::Jspell->new( "dict_name" , "personal_dict_name");
78              
79             $dict->rad("gatinho"); # list of radicals (gato)
80              
81             $dict->fea("gatinho"); # list of possible analysis
82              
83             $dict->der("gato"); # list of derivated words
84              
85             $dict->flags("gato"); # list of roots and flags
86              
87             =head1 FUNCTIONS
88              
89              
90             =head2 new
91              
92             Use to open a dictionary. Pass it the dictionary name and optionally a
93             personal dictionary name. A new jspell dictionary object will be
94             returned.
95              
96             =cut
97              
98             sub new {
99 0     0 1   my ($self, $dr, $pers, $flag);
100 0           local $/="\n";
101 0           my $class = shift;
102              
103 0           $self->{dictionary} = shift;
104             $self->{pdictionary} = shift ||
105 0   0       (defined($ENV{HOME})?"$ENV{HOME}/.jspell.$self->{dictionary}":"");
106              
107 0 0         $pers = $self->{pdictionary}?"-p $self->{pdictionary}":"";
108 0 0         $flag = defined($self->{'undef'})?$self->{'undef'}:"-y";
109              
110             ## Get yaml info ----------------------------------
111 0           my $yaml_file = _yaml_file($self->{dictionary});
112 0 0         if (-f $yaml_file) {
113 0           $self->{yaml} = LoadFile($yaml_file);
114             } else {
115 0           $self->{yaml} = {};
116             }
117              
118              
119 0           my $js = "$JSPELL -d $self->{dictionary} -a $pers -W 0 $flag -o'%s!%s:%s:%s:%s'";
120 0           local $.;
121 0 0         $self->{pid} = open3($self->{DW},$self->{DR},$self->{DE},$js) or die $!;
122            
123 0           binmode($self->{DW},":encoding(iso-8859-1)");
124 0 0         if ($^O ne "MSWin32") {
125 0           binmode($self->{DR},":encoding(iso-8859-1)");
126             }
127             else {
128 0           binmode($self->{DR},":crlf:encoding(iso-8859-1)");
129             }
130 0           $dr = $self->{DR};
131 0           my $first_line = <$dr>;
132 0 0 0       die "Can't execute jspell with supplied dictionaries ($js)" unless $first_line && $first_line =~ /International Jspell/;
133              
134 0   0       $self->{mode} ||= $MODE;
135 0           my $dw = $self->{DW};
136 0           print $dw _mode($self->{mode});
137              
138 0 0         if ($first_line =~ /Jspell/) {
139 0           return bless $self, $class # amen
140             }
141             else {
142             return undef
143 0           }
144             }
145              
146             =head2 nearmatches
147              
148             This method returns a list of analysis for words that are near-matches
149             to the supplied word. Note that although a word might exist, this
150             method will compute the near-matches as well.
151              
152             @nearmatches = $dictionary->nearmatches('cavale');
153              
154             To compute the list of words to analyze, the method uses a list of
155             equivalence classes that are present on the C<< SNDCLASSES >> section
156             of dictionaries yaml files.
157              
158             It is also possible to specify a list of user-defined classes. These
159             are supplied as a filename that contains, per line, the characters
160             that are equivalent (with spaces separating them):
161              
162             ch x
163             ss ç
164              
165             This example says that if a word uses C, then it can be replaced
166             by C for near-matches calculation. The inverse is also true.
167              
168             If these rules are stored in a file named C, you can
169             supply this list with:
170              
171             @nearmatches = $dictionary->nearmatches('chaile', rules => 'classes.txt');
172              
173             =cut
174              
175             sub nearmatches {
176 0     0 1   my ($dict, $word, %ops) = @_;
177 0           my %classes;
178 0 0         if ($ops{rules}) {
179 0 0         -f $ops{rules} or die "Can't find file $ops{rules}";
180 0           local $.;
181 0 0         open RULES, $ops{rules} or die "Can't open file $ops{rules}";
182 0           my @rules;
183 0           while() {
184 0           chomp;
185 0           push @rules, [split /\s+/];
186             }
187 0           close RULES;
188 0           %classes = _expand_classes(@rules);
189             } else {
190 0 0         if (exists($dict->{yaml}{META}{SNDCLASSES})) {
191 0           %classes = _expand_classes(@{ $dict->{yaml}{META}{SNDCLASSES} });
  0            
192             } else {
193 0           warn "No snd classes defined\n";
194             }
195             }
196              
197 0           my @words = ();
198 0           for my $c (keys %classes) {
199 0           my @where;
200 0           my $l = length($c);
201 0           push @where, pos($word)-$l while $word =~ /$c/g;
202 0           for my $i (@where) {
203 0           my $o = $word;
204 0           substr($o,$i,length($c), $classes{$c});
205 0 0         push @words, $o if $o ne $word;
206             }
207             }
208              
209 0           my $current_mode = $dict->setmode;
210 0           $dict->setmode({flags => 0, nm => "cc" });
211              
212 0           my @nms;
213 0           for my $w (@words) {
214 0   0       my @analysis = map { $_->{guess}||=$w; $_ } $dict->fea($w);
  0            
  0            
215 0           push @nms, @analysis;
216             }
217              
218 0           @nms = grep { $_->{guess} ne $word } @nms;
  0            
219             # This one is not a guess
220 0           push @nms, $dict->fea($word);
221              
222 0           @nms = _remove_dups(@nms);
223              
224 0           $dict->setmode($current_mode);
225 0           return @nms;
226             }
227              
228             sub _remove_dups {
229 0     0     my @new;
230 0           while (my $struct = shift @_) {
231 0 0         push @new, $struct unless grep { Compare($_,$struct) } @new;
  0            
232             }
233 0           @new;
234             }
235              
236 0     0     sub _expand_classes { map { _expand_class($_) } @_ }
  0            
237              
238             sub _expand_class {
239 0     0     my @class = @{ $_[0] };
  0            
240 0           my %subs;
241 0           for my $c (@class) {
242 0           my @other = grep { $_ ne $c } @class;
  0            
243 0           for (@other) {
244 0           $subs{$c} = $_;
245             }
246             }
247             %subs
248 0           }
249              
250             =head2 setmode
251              
252             $dict->setmode({flags => 0, nm => "off" });
253              
254             =over 4
255              
256             =item af
257              
258             (add flags) Enable parcial near misses, by using rules not officially
259             associated with the current word. Does not give suggestions by
260             changing letters on the original word. (default option)
261              
262             =item full
263              
264             (add flags and change characters) Enable near misses, try to use rules
265             where they are not applied, try to give suggestions by swapping
266             adjacent letters on the original word.
267              
268             =item cc
269              
270             (change characters) Enable parcial near misses, by swapping adjacent,
271             inserting or modifying letters on the original word. Does not use
272             rules not associated with the current word.
273              
274             =item off
275              
276             Disable near misses at all.
277              
278             =back
279              
280             =cut
281              
282             sub setmode {
283 0     0 1   my ($self, $mode) = @_;
284              
285 0           my $dw = $self->{DW};
286 0 0         if (defined($mode)) {
287 0           $self->{mode} = $mode;
288 0           print $dw _mode($mode);
289             } else {
290 0           return $self->{mode};
291             }
292             }
293              
294             =head2 fea
295              
296             Returns a list of analisys of a word. Each analisys is a list of
297             attribute value pairs. Attributes available: CAT, T, G, N, P, ....
298              
299             @l = $dic->fea($word)
300             @l = $dic->fea($word,{...att. value pair restriction})
301              
302             If a restriction is provided, just the analisys that verify
303             it are returned.
304              
305             =cut
306              
307              
308             sub fea {
309 0     0 1   my ( $self, $w, $res ) = @_;
310              
311 0           local $/ = "\n";
312              
313 0           my @r = ();
314 0           my ( $a, $rad, $cla, $flags );
315              
316 0 0         if ( $w =~ /\!/ ) {
317 0           @r = ( +{ CAT => 'punct', rad => '!' } );
318             }
319             else {
320 0           my ( $dw, $dr ) = ( $self->{DW}, $self->{DR} );
321              
322 0           local $.;
323              
324 0           print $dw " $w\n";
325 0           $a = <$dr>;
326              
327 0           for ( ; ( $a ne "\n" ); $a = <$dr> ) { # l^e as respostas
328 0           for ($a) {
329 0           chop;
330 0           my ( $lixo, $clas );
331 0 0         if (/(.*?) :(.*)/) { $clas = $2; $lixo = $1 }
  0            
  0            
332 0           else { $clas = $_; $lixo = "" }
  0            
333              
334 0           for ( split( /[,;] /, $clas ) ) {
335 0           ( $rad, $cla ) = m{(.+?)\!:*(.*)$};
336              
337             # $cla undef quando nada preenchido...
338              
339 0 0         if ($cla) {
340 0 0         if ( $cla =~ s/\/(.*)$// ) { $flags = $1 }
  0            
341 0           else { $flags = "" }
342              
343 0           $cla =~ s/:+$//g;
344 0           $cla =~ s/:+/,/g;
345              
346 0           my %ana = ();
347 0           my @attrs = split /,/, $cla;
348 0           for (@attrs) {
349 0 0         if (m!=!) {
350 0           $ana{$`} = $';
351             }
352             else {
353 0           print STDERR
354             "** WARNING: Feature-structure parse error: $cla (for word '$w')\n";
355             }
356             }
357              
358 0 0         $ana{"flags"} = $flags if $flags;
359              
360 0 0         if ( $lixo =~ /^&/ ) {
361 0           $rad =~ s/(.*?)= //;
362 0           $ana{"guess"} = lc($1);
363 0           $ana{"unknown"} = 1;
364             }
365 0 0         if ( $rad ne "" ) {
366 0           push( @r, +{ "rad" => $rad, %ana } );
367             }
368             }
369             else {
370 0           @r = ( +{ CAT => "?", rad => $rad } );
371             }
372             }
373             }
374             }
375             }
376 0 0         if ($res) {
377 0           return ( grep { verif( $res, $_ ) } @r );
  0            
378             }
379 0           else { return @r; }
380             }
381              
382             =head2 flags
383              
384             returns the set of morphological flag associated with the word.
385             Each flag is related with a set of morphological rules.
386              
387             @f = flags("gato")
388              
389             =cut
390              
391             sub flags {
392 0     0 1   my $self = shift;
393 0           my $w = shift;
394 0           my ($a,$dr);
395 0           local $/="\n";
396              
397 0           local $.;
398              
399 0           print {$self->{DW}} "\$\"$w\n";
  0            
400 0           $dr = $self->{DR};
401 0           $a = <$dr>;
402              
403 0           chop $a;
404 0           return split(/[# ,]+/,$a);
405             }
406              
407             =head2 rad
408              
409             Returns the list of all possible radicals/lemmas for the supplied word.
410              
411             @l = $dic->rad($word)
412              
413             =cut
414              
415             sub rad {
416 0     0 1   my $self = shift;
417 0           my $word = shift;
418              
419 0 0         return () if $word =~ /\!/;
420              
421 0           my %rad = ();
422 0           my $a_ = "";
423 0           local $/ = "\n";
424 0           local $.;
425            
426 0           my ($dw,$dr) = ($self->{DW},$self->{DR});
427              
428 0           print $dw " $word\n";
429              
430            
431 0           for ($a_ = <$dr>; $a_ ne "\n"; $a_ = <$dr>) {
432 0           chop $a_;
433 0           %rad = ($a_ =~ m/(?: |:)([^ =:,!]+)(\!)/g ) ;
434             }
435              
436 0           return (keys %rad);
437             }
438              
439              
440             =head2 der
441              
442             Returns the list of all possible words using the word as radical.
443              
444             @l = $dic->der($word);
445              
446             =cut
447              
448             sub der {
449 0     0 1   my ($self, $w) = @_;
450 0           my @der = $self->flags($w);
451 0           my %res = ();
452 0           my $command;
453              
454 0           local $/ = "\n";
455 0           local $.;
456 0 0         my $pid = open3(\*WR, \*RD, \*ERROR, "$JSPELL -d $self->{dictionary} -e -o \"\"") or die "Can't execute jspell.";
457 0           print WR join("\n",@der),"\n";
458 0 0         print WR "\032" if ($^O =~ /win32/i);
459 0           close WR;
460 0           while () {
461 0           chomp;
462 0           s/(=|, | $)//g;
463 0           for(split) { $res{$_}++; }
  0            
464             }
465 0           close RD;
466 0           close ERROR;
467 0           waitpid $pid, 0;
468            
469 0           my $irrcomm;
470 0           my $irr_file = _irr_file($self->{dictionary});
471              
472 0           local $.;
473 0 0         if (open IRR, $irr_file) {
474 0           while () {
475 0 0         next unless /^\Q$w\E=/;
476 0           chomp;
477 0           for (split(/[= ]+/,$_)) { $res{$_}++; }
  0            
478             }
479 0           close IRR;
480             }
481 0           return keys %res;
482             }
483              
484             =head2 onethat
485              
486             Returns the first Feature Structure from the supplied list that
487             verifies the Feature Structure Pattern used.
488              
489             %analysis = onethat( { CAT=>'adj' }, @features);
490              
491             %analysis = onethat( { CAT=>'adj' }, $pt->fea("espanhol"));
492              
493             =cut
494              
495             sub onethat {
496 0     0 1   my ($a, @b) = @_;
497 0           for (@b) {
498 0 0         return %$_ if verif($a,$_);
499             }
500 0           return () ;
501             }
502              
503             =head2 verif
504              
505             Returns a true value if the second Feature Structure verifies the
506             first Feature Structure Pattern.
507              
508             if (verif( $pattern, $feature) ) { ... }
509              
510             =cut
511              
512             sub verif {
513 0     0 1   my ($a, $b) = @_;
514 0           for (keys %$a) {
515 0 0 0       return 0 if (!defined($b->{$_}) || $a->{$_} ne $b->{$_});
516             }
517 0           return 1;
518             }
519              
520             =head2 nlgrep
521              
522             @line = $d->nlgrep( word , files);
523             @line = $d->nlgrep( [word1, wordn] , files);
524              
525             or with options to set a max number of entries, rec. separator, or tu use
526             radtxt files format.
527              
528             @line = $d->nlgrep( {max=>100, sep => "\n", radtxt=>0} , pattern , files);
529              
530             =cut
531              
532             sub nlgrep {
533 0     0 1   my ($self ) = shift;
534             # max=int, sep:str, radtxt:bool
535 0           my %opt = (max=>10000, sep => "\n",radtxt=>0);
536 0 0         %opt = (%opt,%{shift(@_)}) if ref($_[0]) eq "HASH";
  0            
537              
538 0           my $p = shift;
539              
540 0 0 0       if(!ref($p) && $p =~ /[ ()*,]/){
541 0 0         $p = [map {/\w/ ? ($_):()} split(/[\- ()*\|,]/,$a)];}
  0            
542              
543 0           my $p2 ;
544              
545 0 0         if(ref($p) eq "ARRAY"){
546 0 0         if($opt{radtxt}){
547 0           my @pat = @$p ;
548 0     0     $p2 = sub{ my $x=shift;
549 0 0         for(@pat){ return 0 unless $x =~ /\b(?:$_)\b/i;}
  0            
550 0           return 1; };
  0            
551             }
552             else {
553 0           my @pat = map {join("|",($_,$self->der($_)))} @$p ;
  0            
554 0     0     $p2 = sub{ my $x=shift;
555 0 0         for(@pat){ return 0 unless $x =~ /\b(?:$_)\b/i;}
  0            
556 0           return 1; }
557 0           }
558             }
559             else {
560 0 0         my $pattern = $opt{radtxt} ? $p : join("|",($p,$self->der($p)));
561 0     0     $p2 = sub{ $_[0] =~ /\b(?:$pattern)\b/i };
  0            
562             }
563              
564 0           my @file_list=@_;
565 0           local $/=$opt{sep};
566              
567 0           my @res=();
568 0           my $n = 0;
569 0           for(@file_list) {
570 0           local $.;
571 0 0         open(F,$_) or die("cant open $_\n");
572 0           while() {
573 0 0         if ($p2->($_)) {
574 0           chomp;
575 0 0         s/$DELIM.*//g if $opt{radtxt};
576 0           push(@res,$_);
577 0 0         last if $n++ == $opt{max};
578             }
579             }
580 0           close F;
581 0 0         last if $n == $opt{max};
582             }
583 0           return @res;
584             }
585              
586             =head2 setstopwords
587              
588             =cut
589              
590             sub setstopwords {
591 0     0 1   $STOP{$_} = 1 for @_;
592             }
593              
594             =head2 eagles
595              
596             =cut
597             sub eagles {
598 0     0 1   my ($dict, $palavra, @ar) = @_;
599              
600             map {
601 0           my $fea = $_;
  0            
602 0           map { $_ . ":$fea->{rad}" } Lingua::Jspell::EAGLES::_cat2eagles(%$fea)
  0            
603             } $dict->fea($palavra, @ar);
604             }
605              
606             # NOTA: Esta funcao é específica da língua TUGA!
607             sub _cat2small {
608 0     0     my %b = @_;
609             # no warnings;
610              
611 0   0       $b{CAT} ||= "HEY!";
612 0   0       $b{G} ||= "";
613 0   0       $b{N} ||= "";
614 0   0       $b{P} ||= "";
615 0   0       $b{T} ||= "";
616              
617 0 0 0       if ($b{CAT} eq 'art') {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
618             # Artigos: o léxico já prevê todos...
619             # por isso, NUNCA SE DEVE CHEGAR AQUI!!!
620 0           return "ART";
621             # 16 tags
622              
623             } elsif ($b{CAT} eq 'card') {
624             # Numerais cardinais:
625 0           return "DNCNP";
626             # o léxico já prevê os que flectem (1 e 2); o resto é tudo neutro plural.
627              
628             } elsif ($b{CAT} eq 'nord') {
629             # Numerais ordinais:
630 0           return "\UDNO$b{G}$b{N}";
631              
632             } elsif ($b{CAT} eq 'ppes' || $b{CAT} eq 'prel' ||
633             $b{CAT} eq 'ppos' || $b{CAT} eq 'pdem' ||
634             $b{CAT} eq 'pind' || $b{CAT} eq 'pint') {
635             # Pronomes:
636 0 0         if ($b{CAT} eq 'ppes') {
    0          
    0          
    0          
    0          
    0          
637             # Pronomes pessoais
638 0           $b{CAT} = 'PS';
639             } elsif ($b{CAT} eq 'prel') {
640             # Pronomes relativos
641 0           $b{CAT} = 'PR';
642             } elsif ($b{CAT} eq 'ppos') {
643             # Pronomes possessivos
644 0           $b{CAT} = 'PP';
645             } elsif ($b{CAT} eq 'pdem') {
646             # Pronomes demonstrativos
647 0           $b{CAT} = 'PD';
648             } elsif ($b{CAT} eq 'pint') {
649             # Pronomes interrogativos
650 0           $b{CAT} = 'PI';
651             } elsif ($b{CAT} eq 'pind') {
652             # Pronomes indefinidos
653 0           $b{CAT} = 'PF';
654             }
655              
656 0 0         $b{G} = 'N' if $b{G} eq '_';
657 0 0         $b{N} = 'N' if $b{N} eq '_';
658              
659             # $b{C} esta por inicializar... oops!? vou por como C para já
660 0           $b{C} = "C";
661 0           return "\U$b{CAT}$b{'C'}$b{G}$b{'P'}$b{N}";
662             # $b{'C'}: caso latino.
663              
664             } elsif ($b{CAT} eq 'nc') {
665             # Nomes comuns:
666 0 0 0       $b{G} = 'N' if $b{G} eq '_' || $b{G} eq '';
667 0 0 0       $b{N} = 'N' if $b{N} eq '_' || $b{N} eq '';
668 0   0       $b{GR} ||= '' ;
669 0 0         $b{GR}= 'd' if $b{GR} eq 'dim';
670 0           return "\U$b{CAT}$b{G}$b{N}$b{GR}";
671              
672             } elsif ($b{CAT} eq 'np') {
673             # Nomes próprios:
674 0 0 0       $b{G} = 'N' if $b{G} eq '_' || $b{G} eq '';
675 0 0 0       $b{N} = 'N' if $b{N} eq '_' || $b{N} eq '';
676 0           return "\U$b{CAT}$b{G}$b{N}";
677              
678             } elsif ($b{CAT} eq 'adj') {
679             # Adjectivos:
680 0 0         $b{G} = 'N' if $b{G} eq '_';
681 0 0         $b{G} = 'N' if $b{G} eq '2';
682 0 0         $b{N} = 'N' if $b{N} eq '_';
683 0   0       $b{GR} ||= '' ;
684 0 0         $b{GR} = 'd' if $b{GR} eq 'dim';
685             # elsif ($b{N} eq ''){
686             # $b{N} = 'N';
687             # }
688 0           return "\UJ$b{G}$b{N}$b{GR}";
689              
690             } elsif ($b{CAT} eq 'a_nc') {
691             # Adjectivos que podem funcionar como nomes comuns:
692 0 0         $b{G} = 'N' if $b{G} eq '_';
693 0 0         $b{G} = 'N' if $b{G} eq '2';
694 0 0         $b{N} = 'N' if $b{N} eq '_';
695 0   0       $b{GR} ||= '' ;
696 0 0         $b{GR} = 'd' if $b{GR} eq 'dim';
697             # elsif ($b{N} eq ''){
698             # $b{N} = 'N';
699             # }
700 0           return "\UX$b{G}$b{N}$b{GR}";
701              
702             } elsif ($b{CAT} eq 'v') {
703             # Verbos:
704              
705             # formas nominais:
706 0 0         if ($b{T} eq 'inf') {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
707             # infinitivo impessoal
708 0           $b{T} = 'N';
709              
710             } elsif ($b{T} eq 'ppa') {
711             # Particípio Passado
712 0           $b{T} = 'PP';
713              
714             } elsif ($b{T} eq 'g') {
715             # Gerúndio
716 0           $b{T} = 'G';
717              
718             } elsif ($b{T} eq 'p') {
719             # modo indicativo: presente (Hoje)
720 0           $b{T} = 'IH';
721              
722             } elsif ($b{T} eq 'pp') {
723             # modo indicativo: pretérito Perfeito
724 0           $b{T} = 'IP';
725              
726             } elsif ($b{T} eq 'pi') {
727             # modo indicativo: pretérito Imperfeito
728 0           $b{T} = 'II';
729              
730             } elsif ($b{T} eq 'pmp') {
731             # modo indicativo: pretérito Mais-que-perfeito
732 0           $b{T} = 'IM';
733              
734             } elsif ($b{T} eq 'f') {
735             # modo indicativo: Futuro
736 0           $b{T} = 'IF';
737              
738             } elsif ($b{T} eq 'pc') {
739             # modo conjuntivo (Se): presente (Hoje)
740 0           $b{T} = 'SH';
741              
742             } elsif ($b{T} eq 'pic') {
743             # modo conjuntivo (Se): pretérito Imperfeito
744 0           $b{T} = 'SI';
745              
746             } elsif ($b{T} eq 'fc') {
747             # modo conjuntivo (Se): Futuro
748 0           $b{T} = 'PI';
749              
750             } elsif ($b{T} eq 'i') {
751             # modo Imperativo: presente (Hoje)
752 0           $b{T} = 'MH';
753              
754             } elsif ($b{T} eq 'c') {
755             # modo Condicional: presente (Hoje)
756 0           $b{T} = 'CH';
757              
758             } elsif ($b{T} eq 'ip') {
759             # modo Infinitivo (Pessoal ou Presente):
760 0           $b{T} = 'PI';
761              
762             # Futuro conjuntivo? Só se tiver um "se" antes! -> regras sintácticas...
763             # modo&tempo não previstos ainda...
764              
765             } else {
766 0           $b{T} = '_UNKNOWN';
767             }
768              
769             # converter 'P=1_3' em 'P=_': provisório(?)!
770 0           $b{P} = "";
771 0 0         $b{P} = '_' if $b{P} eq '1_3'; # único sítio com '_' como rhs!!!
772              
773            
774 0 0         if ($b{T} eq "vpp") { return "\U$b{CAT}$b{T}$b{G}$b{P}$b{N}"; }
  0            
775 0           else { return "\U$b{CAT}$b{T}$b{P}$b{N}"; }
776              
777              
778             # Género, só para VPP.
779             # +/- 70 tags
780              
781             } elsif ($b{CAT} eq 'prep') {
782             # Preposições¹:
783 0           return "\UP";
784              
785             } elsif ($b{CAT} eq 'adv') {
786             # Advérbios²:
787 0           return "\UADV";
788              
789             } elsif ($b{CAT} eq 'con') {
790             # Conjunções²:
791 0           return "\UC";
792              
793             } elsif ($b{CAT} eq 'in') {
794             # Interjeições¹:
795 0           return "\UI";
796              
797             # ¹: não sei se a tag devia ser tão atómica, mas para já não há confusão!
798              
799             } elsif ($b{CAT} =~ m/^cp(.*)/) {
800             # Contracções¹:
801 0 0         $b{G} = 'N' if $b{G} eq '_';
802 0 0         $b{N} = 'N' if $b{N} eq '_';
803 0           return "\U&$b{G}$b{N}";
804              
805             # ²: falta estruturar estes no próprio dicionário...
806             # Palavras do dicionário com categoria vazia ou sem categoria,
807             # palavras não existentes ou sequências aleatórias de caracteres:
808              
809             } elsif (defined($b{CAT}) && $b{CAT} eq '') {
810 0           return "\UUNDEFINED";
811              
812             } else { # restantes categorias (...?)
813 0           return "\UUNTREATED";
814             }
815             }
816              
817             =head2 new_featags
818              
819             =cut
820              
821             sub new_featags {
822 0     0 1   my ($self, $word) = @_;
823 0 0         if (exists($self->{yaml}{META}{TAG})) {
824 0           my $rules = $self->{yaml}{META}{TAG};
825 0           return map { $self->_compact($rules, $_) } $self->fea($word);
  0            
826             } else {
827 0           warn "Dictionary without a YAML file, or without rules for fea-compression\n";
828 0           return undef;
829             }
830             }
831              
832             sub _compact {
833 0     0     my ($self,$rules, $fs) = @_;
834 0           my $tag;
835 0 0         if (ref($rules) eq "HASH") {
    0          
    0          
836 0           my ($key) = (%$rules);
837              
838 0 0         if (exists($fs->{$key})) {
839 0           $tag = $self->_compact_id($key, $fs->{$key});
840 0 0         if (exists($rules->{$key}{$fs->{$key}})) {
    0          
841 0           $tag.$self->_compact($rules->{$key}{$fs->{$key}}, $fs);
842             }
843             elsif (exists($rules->{$key}{'-'})) {
844 0           $tag.$self->_compact($rules->{$key}{'-'}, $fs);
845             }
846             else {
847 0           $tag
848             }
849             }
850             else {
851 0           ""
852             }
853             }
854             elsif (ref($rules) eq "ARRAY") {
855 0           for my $cat (@$rules) {
856 0           $tag .= $self->_compact($cat, $fs);
857             }
858             $tag
859 0           }
860             elsif (!ref($rules)) {
861 0 0 0       if ($rules && exists($fs->{$rules})) {
862 0           $self->_compact_id($rules, $fs->{$rules})
863             } else {
864 0           ""
865             }
866             }
867             }
868              
869             sub _compact_id {
870 0     0     my ($self, $cat, $id) = @_;
871 0 0         if (exists($self->{yaml}{"$cat-TAG"}{$id})) {
872 0           return $self->{yaml}{"$cat-TAG"}{$id}
873             } else {
874 0           return $id
875             }
876             }
877              
878              
879             =head2 featags
880              
881             Given a word, returns a set of analysis. Each analysis is a morphosintatic tag
882              
883             @l= $pt->featags("lindas")
884             JFS , ...
885             @l= $pt->featags("era",{CAT=>"v"}) ## with a constraint
886              
887              
888             =cut
889              
890             sub featags{
891 0     0 1   my ($self, $palavra,@Ar) = @_;
892 0           return (map {_cat2small(%$_)} ($self->fea($palavra,@Ar)));
  0            
893             }
894              
895             =head2 featagsrad
896              
897             Given a word, returns a set of analysis. Each analysis is a morphosintatic tag
898             and the lemma information
899              
900             @l= $pt->featagsrad("lindas")
901             JFS:lindo , ...
902             @l= $pt->featagsrad("era",{CAT=>"v"}) ## with a constraint
903              
904             =cut
905              
906             sub featagsrad{
907 0     0 1   my ($self, $palavra,@Ar) = @_;
908              
909 0           return (map {_cat2small(%$_).":$_->{rad}"} ($self->fea($palavra,@Ar)));
  0            
910             }
911              
912              
913             =head2 onethatverif
914              
915             Given a pattern feature structure and a list of analysis (feature
916             structures), returns a true value is there is one analysis that
917             verifies the pattern.
918              
919             # onethatverif( cond:fs , conj:fs-set) :: bool
920             # exists x in conj: verif(cond , x)
921              
922             if(onethatverif({CAT=>"adj"},$pt->fea("linda"))) {
923             ...
924             }
925              
926             =cut
927              
928             sub onethatverif {
929 0     0 1   my ($a, @b) = @_;
930 0           for (@b) {
931 0 0         return 1 if verif($a,$_);
932             }
933 0           return 0 ;
934             }
935              
936             =head2 mkradtxt
937              
938             =cut
939              
940             sub mkradtxt {
941 0     0 1   my ($self, $f1, $f2) = @_;
942 0           local $.;
943 0 0         open F1, $f1 or die "Can't open '$f1'\n";
944 0 0         open F2, "> $f2" or die "Can't create '$f2'\n";
945 0           while() {
946 0           chomp;
947 0           print F2 "$_$DELIM";
948 0           while (/((\w|-)+)/g) {
949 0 0         print F2 " ",join(" ",$self->rad($1)) unless $STOP{$1}
950             }
951 0           print F2 "\n";
952             }
953 0           close F1;
954 0           close F2;
955             }
956              
957             =head2 isguess
958              
959             Lingua::Jspell::isguess(@ana)
960              
961             returns True if list of analisys are near
962             misses (unknown attribut is 1).
963              
964             =cut
965              
966             sub isguess{
967 0     0 1   my @a=@_;
968 0   0       return @a && $a[0]{unknown};
969             }
970              
971             =head2 any2str
972              
973             Lingua::Jspell::any2str($ref)
974             Lingua::Jspell::any2str($ref,$indentation)
975             Lingua::Jspell::any2str($ref,"compact")
976              
977             =cut
978              
979             sub any2str {
980 0     0 1   my ($r, $i) = @_;
981 0   0       $i ||= 0;
982 0 0         if (not $r) {return ""}
  0            
983 0 0         if (ref $i) { any2str([@_]);}
  0 0          
    0          
984             elsif ($i eq "compact") {
985 0 0         if (ref($r) eq "HASH") {
    0          
986 0           return "{". hash2str($r,$i) . "}"
987             } elsif (ref($r) eq "ARRAY") {
988 0           return "[" . join(",", map (any2str($_,$i), @$r)) . "]"
989             } else {
990 0           return "$r"
991             }
992             } elsif ($i eq "f1") {
993 0 0         if (ref($r) eq "HASH") {
    0          
994 0           return "{". hash2str($r,"f1") . "}"
995             } elsif (ref($r) eq "ARRAY") {
996 0           return "[ " . join(" ,\n ", map (any2str($_,"compact"), @$r)) . "]"
997             } else {
998 0           return "$r"
999             }
1000             } else {
1001 0 0         my $ind = ($i >= 0)? (" " x $i) : "";
1002 0 0         if (ref($r) eq "HASH") {
    0          
1003 0           return "$ind {". hash2str($r,abs($i)+3) . "}"
1004             } elsif (ref($r) eq "ARRAY") {
1005 0           return "$ind [\n" . join("\n", map (any2str($_,abs($i)+3), @$r)) . "]"
1006             } else {
1007 0           return "$ind$r"
1008             }
1009             }
1010             }
1011              
1012             =head2 hash2str
1013              
1014             =cut
1015              
1016             sub hash2str {
1017 0     0 1   my ($r, $i) = @_;
1018 0           my $c = "";
1019 0 0         if ($i eq "compact") {
    0          
1020 0           for (keys %$r) {
1021 0           $c .= any2str($_,$i). "=". any2str($r->{$_},$i). ",";
1022             }
1023 0           chop($c);
1024             } elsif ($i eq "f1") {
1025 0           for (keys %$r) {
1026 0           $c .= "\n ". any2str($_,"compact"). "=". any2str($r->{$_},"compact"). "\n";
1027             }
1028 0           chop($c);
1029             } else {
1030 0           for (keys %$r) {
1031 0           $c .= "\n". any2str($_,$i). " => ". any2str($r->{$_},-$i);
1032             }
1033             }
1034 0           return $c;
1035             }
1036              
1037             =head1 AUTHOR
1038              
1039             Jose Joao Almeida, C<< >>
1040             Alberto Simões, C<< >>
1041              
1042             =head1 BUGS
1043              
1044             Please report any bugs or feature requests to
1045             C, or through the web interface at
1046             L. I
1047             will be notified, and then you'll automatically be notified of
1048             progress on your bug as I make changes.
1049              
1050             =head1 COPYRIGHT & LICENSE
1051              
1052             Copyright 2007-2009 Projecto Natura
1053              
1054             This program is free software; licensed under GPL.
1055              
1056             =cut
1057              
1058             sub _yaml_file {
1059 0     0     my $dic_file = shift;
1060 0 0         if ($dic_file =~ m!\.hash$!) {
1061             # we have a local dictionary
1062 0           $dic_file =~ s/\.hash/.yaml/;
1063             } else {
1064 0           $dic_file = "$JSPELLLIB/$dic_file.yaml"
1065             }
1066 0           return $dic_file;
1067             }
1068              
1069             sub _mode {
1070 0     0     my $m = shift;
1071 0           my $r="";
1072 0 0         if ($m->{nm}) {
1073 0 0         if ($m->{nm} eq "af") ### af = GPy --> Gym
    0          
    0          
    0          
1074 0           { $r .= "\$G\n\$m\n\$y\n" }
1075             elsif ($m->{nm} eq "full") ### full = GYm
1076 0           { $r .= "\$G\n\$Y\n\$m\n" }
1077             elsif ($m->{nm} eq "cc") ### cc = GPY
1078 0           { $r .= "\$G\n\$P\n\$Y\n" }
1079             elsif ($m->{nm} eq "off") ### off = gPy
1080 0           { $r .= "\$g\n\$P\n\$y\n" }
1081             else {}
1082             }
1083 0 0         if ($m->{flags}) {$r .= "\$z\n"}
  0            
1084 0           else {$r .= "\$Z\n"}
1085 0           return $r;
1086             }
1087              
1088              
1089             sub _irr_file {
1090 0     0     my $irr_file = shift;
1091 0 0         if ($irr_file =~ m!\.hash$!) {
1092             # we have a local dictionary
1093 0           $irr_file =~ s/\.hash/.irr/;
1094             } else {
1095 0           $irr_file = "$JSPELLLIB/$irr_file.irr"
1096             }
1097 0           return $irr_file;
1098             }
1099              
1100              
1101              
1102              
1103             '\o/ yay!'; # End of Lingua::Jspell
1104              
1105             __END__