File Coverage

blib/lib/Lingua/Jspell.pm
Criterion Covered Total %
statement 49 475 10.3
branch 7 274 2.5
condition 0 72 0.0
subroutine 13 46 28.2
pod 21 21 100.0
total 90 888 10.1


line stmt bran cond sub pod time code
1             package Lingua::Jspell;
2              
3 2     2   78736 use warnings;
  2         6  
  2         69  
4 2     2   10 use strict;
  2         9  
  2         42  
5              
6 2     2   40 use 5.008001;
  2         8  
7              
8 2     2   1157 use POSIX qw(locale_h);
  2         13003  
  2         10  
9             setlocale(LC_CTYPE, "pt_PT");
10 2     2   4236 use locale;
  2         1596  
  2         12  
11              
12 2     2   84 use base 'Exporter';
  2         4  
  2         294  
13             our @EXPORT_OK = (qw.allthat 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   997 use File::Spec::Functions;
  2         1658  
  2         170  
21 2     2   842 use Lingua::Jspell::ConfigData;
  2         6  
  2         68  
22 2     2   870 use Lingua::Jspell::EAGLES;
  2         6  
  2         60  
23 2     2   1063 use IPC::Open3;
  2         7981  
  2         110  
24 2     2   971 use YAML qw/LoadFile/;
  2         15964  
  2         110  
25 2     2   1009 use Data::Compare;
  2         27575  
  2         12  
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.97';
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   4538 delete @ENV{qw(IFS CD PATH ENV BASH_ENV)}; # Make %ENV safer
44              
45 2         8 my $EXE = "";
46 2 50       20 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         5 local $_;
55              
56 2         14 $JSPELL = catfile("blib","bin","jspell$EXE");
57 2 50       50 $JSPELL = Lingua::Jspell::ConfigData->config("jspell") unless -x $JSPELL;
58              
59 2 50       29 die "jspell binary cannot be found!\n" unless -x $JSPELL;
60              
61 2         13 local $.;
62 2 50       4328 open X, "$JSPELL -vv|" or die "Can't execute $JSPELL";
63 2         1548 while () {
64 118 100       403 if (/LIBDIR = "([^"]+)"/) {
65 2         44 $JSPELLLIB = $1;
66             }
67             }
68 2         74 close X;
69 2 50       14566 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              
504             =head2 allthat
505              
506             Returns all Feature Structures from the supplied list that
507             verifies the used Feature Structure Pattern.
508              
509             @analyses = allthat( { CAT=>'adj' }, @features);
510              
511             @analyses = allthat( { CAT=>'adj' }, $pt->fea("espanhol"));
512              
513             =cut
514              
515             sub allthat {
516 0     0 1   my ($a, @b) = @_;
517 0           return grep {verif($a, $_)} @b;
  0            
518             }
519              
520              
521             =head2 verif
522              
523             Returns a true value if the second Feature Structure verifies the
524             first Feature Structure Pattern.
525              
526             if (verif( $pattern, $feature) ) { ... }
527              
528             =cut
529              
530             sub verif {
531 0     0 1   my ($a, $b) = @_;
532 0           for (keys %$a) {
533 0 0 0       return 0 if (!defined($b->{$_}) || $a->{$_} ne $b->{$_});
534             }
535 0           return 1;
536             }
537              
538             =head2 nlgrep
539              
540             @line = $d->nlgrep( word , files);
541             @line = $d->nlgrep( [word1, wordn] , files);
542              
543             or with options to set a max number of entries, rec. separator, or tu use
544             radtxt files format.
545              
546             @line = $d->nlgrep( {max=>100, sep => "\n", radtxt=>0} , pattern , files);
547              
548             =cut
549              
550             sub nlgrep {
551 0     0 1   my ($self ) = shift;
552             # max=int, sep:str, radtxt:bool
553 0           my %opt = (max=>10000, sep => "\n",radtxt=>0);
554 0 0         %opt = (%opt,%{shift(@_)}) if ref($_[0]) eq "HASH";
  0            
555              
556 0           my $p = shift;
557              
558 0 0 0       if(!ref($p) && $p =~ /[ ()*,]/){
559 0 0         $p = [map {/\w/ ? ($_):()} split(/[\- ()*\|,]/,$a)];}
  0            
560              
561 0           my $p2 ;
562              
563 0 0         if(ref($p) eq "ARRAY"){
564 0 0         if($opt{radtxt}){
565 0           my @pat = @$p ;
566 0     0     $p2 = sub{ my $x=shift;
567 0 0         for(@pat){ return 0 unless $x =~ /\b(?:$_)\b/i;}
  0            
568 0           return 1; };
  0            
569             }
570             else {
571 0           my @pat = map {join("|",($_,$self->der($_)))} @$p ;
  0            
572 0     0     $p2 = sub{ my $x=shift;
573 0 0         for(@pat){ return 0 unless $x =~ /\b(?:$_)\b/i;}
  0            
574 0           return 1; }
575 0           }
576             }
577             else {
578 0 0         my $pattern = $opt{radtxt} ? $p : join("|",($p,$self->der($p)));
579 0     0     $p2 = sub{ $_[0] =~ /\b(?:$pattern)\b/i };
  0            
580             }
581              
582 0           my @file_list=@_;
583 0           local $/=$opt{sep};
584              
585 0           my @res=();
586 0           my $n = 0;
587 0           for(@file_list) {
588 0           local $.;
589 0 0         open(F,$_) or die("cant open $_\n");
590 0           while() {
591 0 0         if ($p2->($_)) {
592 0           chomp;
593 0 0         s/$DELIM.*//g if $opt{radtxt};
594 0           push(@res,$_);
595 0 0         last if $n++ == $opt{max};
596             }
597             }
598 0           close F;
599 0 0         last if $n == $opt{max};
600             }
601 0           return @res;
602             }
603              
604             =head2 setstopwords
605              
606             =cut
607              
608             sub setstopwords {
609 0     0 1   $STOP{$_} = 1 for @_;
610             }
611              
612             =head2 eagles
613              
614             =cut
615             sub eagles {
616 0     0 1   my ($dict, $palavra, @ar) = @_;
617              
618             map {
619 0           my $fea = $_;
  0            
620 0           map { $_ . ":$fea->{rad}" } Lingua::Jspell::EAGLES::_cat2eagles(%$fea)
  0            
621             } $dict->fea($palavra, @ar);
622             }
623              
624             # NOTA: Esta funcao é específica da língua TUGA!
625             sub _cat2small {
626 0     0     my %b = @_;
627             # no warnings;
628              
629 0   0       $b{CAT} ||= "HEY!";
630 0   0       $b{G} ||= "";
631 0   0       $b{N} ||= "";
632 0   0       $b{P} ||= "";
633 0   0       $b{T} ||= "";
634              
635 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          
636             # Artigos: o léxico já prevê todos...
637             # por isso, NUNCA SE DEVE CHEGAR AQUI!!!
638 0           return "ART";
639             # 16 tags
640              
641             } elsif ($b{CAT} eq 'card') {
642             # Numerais cardinais:
643 0           return "DNCNP";
644             # o léxico já prevê os que flectem (1 e 2); o resto é tudo neutro plural.
645              
646             } elsif ($b{CAT} eq 'nord') {
647             # Numerais ordinais:
648 0           return "\UDNO$b{G}$b{N}";
649              
650             } elsif ($b{CAT} eq 'ppes' || $b{CAT} eq 'prel' ||
651             $b{CAT} eq 'ppos' || $b{CAT} eq 'pdem' ||
652             $b{CAT} eq 'pind' || $b{CAT} eq 'pint') {
653             # Pronomes:
654 0 0         if ($b{CAT} eq 'ppes') {
    0          
    0          
    0          
    0          
    0          
655             # Pronomes pessoais
656 0           $b{CAT} = 'PS';
657             } elsif ($b{CAT} eq 'prel') {
658             # Pronomes relativos
659 0           $b{CAT} = 'PR';
660             } elsif ($b{CAT} eq 'ppos') {
661             # Pronomes possessivos
662 0           $b{CAT} = 'PP';
663             } elsif ($b{CAT} eq 'pdem') {
664             # Pronomes demonstrativos
665 0           $b{CAT} = 'PD';
666             } elsif ($b{CAT} eq 'pint') {
667             # Pronomes interrogativos
668 0           $b{CAT} = 'PI';
669             } elsif ($b{CAT} eq 'pind') {
670             # Pronomes indefinidos
671 0           $b{CAT} = 'PF';
672             }
673              
674 0 0         $b{G} = 'N' if $b{G} eq '_';
675 0 0         $b{N} = 'N' if $b{N} eq '_';
676              
677             # $b{C} esta por inicializar... oops!? vou por como C para já
678 0           $b{C} = "C";
679 0           return "\U$b{CAT}$b{'C'}$b{G}$b{'P'}$b{N}";
680             # $b{'C'}: caso latino.
681              
682             } elsif ($b{CAT} eq 'nc') {
683             # Nomes comuns:
684 0 0 0       $b{G} = 'N' if $b{G} eq '_' || $b{G} eq '';
685 0 0 0       $b{N} = 'N' if $b{N} eq '_' || $b{N} eq '';
686 0   0       $b{GR} ||= '' ;
687 0 0         $b{GR}= 'd' if $b{GR} eq 'dim';
688 0           return "\U$b{CAT}$b{G}$b{N}$b{GR}";
689              
690             } elsif ($b{CAT} eq 'np') {
691             # Nomes próprios:
692 0 0 0       $b{G} = 'N' if $b{G} eq '_' || $b{G} eq '';
693 0 0 0       $b{N} = 'N' if $b{N} eq '_' || $b{N} eq '';
694 0           return "\U$b{CAT}$b{G}$b{N}";
695              
696             } elsif ($b{CAT} eq 'adj') {
697             # Adjectivos:
698 0 0         $b{G} = 'N' if $b{G} eq '_';
699 0 0         $b{G} = 'N' if $b{G} eq '2';
700 0 0         $b{N} = 'N' if $b{N} eq '_';
701 0   0       $b{GR} ||= '' ;
702 0 0         $b{GR} = 'd' if $b{GR} eq 'dim';
703             # elsif ($b{N} eq ''){
704             # $b{N} = 'N';
705             # }
706 0           return "\UJ$b{G}$b{N}$b{GR}";
707              
708             } elsif ($b{CAT} eq 'a_nc') {
709             # Adjectivos que podem funcionar como nomes comuns:
710 0 0         $b{G} = 'N' if $b{G} eq '_';
711 0 0         $b{G} = 'N' if $b{G} eq '2';
712 0 0         $b{N} = 'N' if $b{N} eq '_';
713 0   0       $b{GR} ||= '' ;
714 0 0         $b{GR} = 'd' if $b{GR} eq 'dim';
715             # elsif ($b{N} eq ''){
716             # $b{N} = 'N';
717             # }
718 0           return "\UX$b{G}$b{N}$b{GR}";
719              
720             } elsif ($b{CAT} eq 'v') {
721             # Verbos:
722              
723             # formas nominais:
724 0 0         if ($b{T} eq 'inf') {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
725             # infinitivo impessoal
726 0           $b{T} = 'N';
727              
728             } elsif ($b{T} eq 'ppa') {
729             # Particípio Passado
730 0           $b{T} = 'PP';
731              
732             } elsif ($b{T} eq 'g') {
733             # Gerúndio
734 0           $b{T} = 'G';
735              
736             } elsif ($b{T} eq 'p') {
737             # modo indicativo: presente (Hoje)
738 0           $b{T} = 'IH';
739              
740             } elsif ($b{T} eq 'pp') {
741             # modo indicativo: pretérito Perfeito
742 0           $b{T} = 'IP';
743              
744             } elsif ($b{T} eq 'pi') {
745             # modo indicativo: pretérito Imperfeito
746 0           $b{T} = 'II';
747              
748             } elsif ($b{T} eq 'pmp') {
749             # modo indicativo: pretérito Mais-que-perfeito
750 0           $b{T} = 'IM';
751              
752             } elsif ($b{T} eq 'f') {
753             # modo indicativo: Futuro
754 0           $b{T} = 'IF';
755              
756             } elsif ($b{T} eq 'pc') {
757             # modo conjuntivo (Se): presente (Hoje)
758 0           $b{T} = 'SH';
759              
760             } elsif ($b{T} eq 'pic') {
761             # modo conjuntivo (Se): pretérito Imperfeito
762 0           $b{T} = 'SI';
763              
764             } elsif ($b{T} eq 'fc') {
765             # modo conjuntivo (Se): Futuro
766 0           $b{T} = 'PI';
767              
768             } elsif ($b{T} eq 'i') {
769             # modo Imperativo: presente (Hoje)
770 0           $b{T} = 'MH';
771              
772             } elsif ($b{T} eq 'c') {
773             # modo Condicional: presente (Hoje)
774 0           $b{T} = 'CH';
775              
776             } elsif ($b{T} eq 'ip') {
777             # modo Infinitivo (Pessoal ou Presente):
778 0           $b{T} = 'PI';
779              
780             # Futuro conjuntivo? Só se tiver um "se" antes! -> regras sintácticas...
781             # modo&tempo não previstos ainda...
782              
783             } else {
784 0           $b{T} = '_UNKNOWN';
785             }
786              
787             # converter 'P=1_3' em 'P=_': provisório(?)!
788 0           $b{P} = "";
789 0 0         $b{P} = '_' if $b{P} eq '1_3'; # único sítio com '_' como rhs!!!
790              
791            
792 0 0         if ($b{T} eq "vpp") { return "\U$b{CAT}$b{T}$b{G}$b{P}$b{N}"; }
  0            
793 0           else { return "\U$b{CAT}$b{T}$b{P}$b{N}"; }
794              
795              
796             # Género, só para VPP.
797             # +/- 70 tags
798              
799             } elsif ($b{CAT} eq 'prep') {
800             # Preposições¹:
801 0           return "\UP";
802              
803             } elsif ($b{CAT} eq 'adv') {
804             # Advérbios²:
805 0           return "\UADV";
806              
807             } elsif ($b{CAT} eq 'con') {
808             # Conjunções²:
809 0           return "\UC";
810              
811             } elsif ($b{CAT} eq 'in') {
812             # Interjeições¹:
813 0           return "\UI";
814              
815             # ¹: não sei se a tag devia ser tão atómica, mas para já não há confusão!
816              
817             } elsif ($b{CAT} =~ m/^cp(.*)/) {
818             # Contracções¹:
819 0 0         $b{G} = 'N' if $b{G} eq '_';
820 0 0         $b{N} = 'N' if $b{N} eq '_';
821 0           return "\U&$b{G}$b{N}";
822              
823             # ²: falta estruturar estes no próprio dicionário...
824             # Palavras do dicionário com categoria vazia ou sem categoria,
825             # palavras não existentes ou sequências aleatórias de caracteres:
826              
827             } elsif (defined($b{CAT}) && $b{CAT} eq '') {
828 0           return "\UUNDEFINED";
829              
830             } else { # restantes categorias (...?)
831 0           return "\UUNTREATED";
832             }
833             }
834              
835             =head2 new_featags
836              
837             =cut
838              
839             sub new_featags {
840 0     0 1   my ($self, $word) = @_;
841 0 0         if (exists($self->{yaml}{META}{TAG})) {
842 0           my $rules = $self->{yaml}{META}{TAG};
843 0           return map { $self->_compact($rules, $_) } $self->fea($word);
  0            
844             } else {
845 0           warn "Dictionary without a YAML file, or without rules for fea-compression\n";
846 0           return undef;
847             }
848             }
849              
850             sub _compact {
851 0     0     my ($self,$rules, $fs) = @_;
852 0           my $tag;
853 0 0         if (ref($rules) eq "HASH") {
    0          
    0          
854 0           my ($key) = (%$rules);
855              
856 0 0         if (exists($fs->{$key})) {
857 0           $tag = $self->_compact_id($key, $fs->{$key});
858 0 0         if (exists($rules->{$key}{$fs->{$key}})) {
    0          
859 0           $tag.$self->_compact($rules->{$key}{$fs->{$key}}, $fs);
860             }
861             elsif (exists($rules->{$key}{'-'})) {
862 0           $tag.$self->_compact($rules->{$key}{'-'}, $fs);
863             }
864             else {
865 0           $tag
866             }
867             }
868             else {
869 0           ""
870             }
871             }
872             elsif (ref($rules) eq "ARRAY") {
873 0           for my $cat (@$rules) {
874 0           $tag .= $self->_compact($cat, $fs);
875             }
876             $tag
877 0           }
878             elsif (!ref($rules)) {
879 0 0 0       if ($rules && exists($fs->{$rules})) {
880 0           $self->_compact_id($rules, $fs->{$rules})
881             } else {
882 0           ""
883             }
884             }
885             }
886              
887             sub _compact_id {
888 0     0     my ($self, $cat, $id) = @_;
889 0 0         if (exists($self->{yaml}{"$cat-TAG"}{$id})) {
890 0           return $self->{yaml}{"$cat-TAG"}{$id}
891             } else {
892 0           return $id
893             }
894             }
895              
896              
897             =head2 featags
898              
899             Given a word, returns a set of analysis. Each analysis is a morphosintatic tag
900              
901             @l= $pt->featags("lindas")
902             JFS , ...
903             @l= $pt->featags("era",{CAT=>"v"}) ## with a constraint
904              
905              
906             =cut
907              
908             sub featags{
909 0     0 1   my ($self, $palavra,@Ar) = @_;
910 0           return (map {_cat2small(%$_)} ($self->fea($palavra,@Ar)));
  0            
911             }
912              
913             =head2 featagsrad
914              
915             Given a word, returns a set of analysis. Each analysis is a morphosintatic tag
916             and the lemma information
917              
918             @l= $pt->featagsrad("lindas")
919             JFS:lindo , ...
920             @l= $pt->featagsrad("era",{CAT=>"v"}) ## with a constraint
921              
922             =cut
923              
924             sub featagsrad{
925 0     0 1   my ($self, $palavra,@Ar) = @_;
926              
927 0           return (map {_cat2small(%$_).":$_->{rad}"} ($self->fea($palavra,@Ar)));
  0            
928             }
929              
930              
931             =head2 onethatverif
932              
933             Given a pattern feature structure and a list of analysis (feature
934             structures), returns a true value is there is one analysis that
935             verifies the pattern.
936              
937             # onethatverif( cond:fs , conj:fs-set) :: bool
938             # exists x in conj: verif(cond , x)
939              
940             if(onethatverif({CAT=>"adj"},$pt->fea("linda"))) {
941             ...
942             }
943              
944             =cut
945              
946             sub onethatverif {
947 0     0 1   my ($a, @b) = @_;
948 0           for (@b) {
949 0 0         return 1 if verif($a,$_);
950             }
951 0           return 0 ;
952             }
953              
954             =head2 mkradtxt
955              
956             =cut
957              
958             sub mkradtxt {
959 0     0 1   my ($self, $f1, $f2) = @_;
960 0           local $.;
961 0 0         open F1, $f1 or die "Can't open '$f1'\n";
962 0 0         open F2, "> $f2" or die "Can't create '$f2'\n";
963 0           while() {
964 0           chomp;
965 0           print F2 "$_$DELIM";
966 0           while (/((\w|-)+)/g) {
967 0 0         print F2 " ",join(" ",$self->rad($1)) unless $STOP{$1}
968             }
969 0           print F2 "\n";
970             }
971 0           close F1;
972 0           close F2;
973             }
974              
975             =head2 isguess
976              
977             Lingua::Jspell::isguess(@ana)
978              
979             returns True if list of analisys are near
980             misses (unknown attribut is 1).
981              
982             =cut
983              
984             sub isguess{
985 0     0 1   my @a=@_;
986 0   0       return @a && $a[0]{unknown};
987             }
988              
989             =head2 any2str
990              
991             Lingua::Jspell::any2str($ref)
992             Lingua::Jspell::any2str($ref,$indentation)
993             Lingua::Jspell::any2str($ref,"compact")
994              
995             =cut
996              
997             sub any2str {
998 0     0 1   my ($r, $i) = @_;
999 0   0       $i ||= 0;
1000 0 0         if (not $r) {return ""}
  0            
1001 0 0         if (ref $i) { any2str([@_]);}
  0 0          
    0          
1002             elsif ($i eq "compact") {
1003 0 0         if (ref($r) eq "HASH") {
    0          
1004 0           return "{". hash2str($r,$i) . "}"
1005             } elsif (ref($r) eq "ARRAY") {
1006 0           return "[" . join(",", map (any2str($_,$i), @$r)) . "]"
1007             } else {
1008 0           return "$r"
1009             }
1010             } elsif ($i eq "f1") {
1011 0 0         if (ref($r) eq "HASH") {
    0          
1012 0           return "{". hash2str($r,"f1") . "}"
1013             } elsif (ref($r) eq "ARRAY") {
1014 0           return "[ " . join(" ,\n ", map (any2str($_,"compact"), @$r)) . "]"
1015             } else {
1016 0           return "$r"
1017             }
1018             } else {
1019 0 0         my $ind = ($i >= 0)? (" " x $i) : "";
1020 0 0         if (ref($r) eq "HASH") {
    0          
1021 0           return "$ind {". hash2str($r,abs($i)+3) . "}"
1022             } elsif (ref($r) eq "ARRAY") {
1023 0           return "$ind [\n" . join("\n", map (any2str($_,abs($i)+3), @$r)) . "]"
1024             } else {
1025 0           return "$ind$r"
1026             }
1027             }
1028             }
1029              
1030             =head2 hash2str
1031              
1032             =cut
1033              
1034             sub hash2str {
1035 0     0 1   my ($r, $i) = @_;
1036 0           my $c = "";
1037 0 0         if ($i eq "compact") {
    0          
1038 0           for (keys %$r) {
1039 0           $c .= any2str($_,$i). "=". any2str($r->{$_},$i). ",";
1040             }
1041 0           chop($c);
1042             } elsif ($i eq "f1") {
1043 0           for (keys %$r) {
1044 0           $c .= "\n ". any2str($_,"compact"). "=". any2str($r->{$_},"compact"). "\n";
1045             }
1046 0           chop($c);
1047             } else {
1048 0           for (keys %$r) {
1049 0           $c .= "\n". any2str($_,$i). " => ". any2str($r->{$_},-$i);
1050             }
1051             }
1052 0           return $c;
1053             }
1054              
1055             =head1 AUTHOR
1056              
1057             Jose Joao Almeida, C<< >>
1058             Alberto Simões, C<< >>
1059              
1060             =head1 BUGS
1061              
1062             Please report any bugs or feature requests to
1063             C, or through the web interface at
1064             L. I
1065             will be notified, and then you'll automatically be notified of
1066             progress on your bug as I make changes.
1067              
1068             =head1 COPYRIGHT & LICENSE
1069              
1070             Copyright 2007-2009 Projecto Natura
1071              
1072             This program is free software; licensed under GPL.
1073              
1074             =cut
1075              
1076             sub _yaml_file {
1077 0     0     my $dic_file = shift;
1078 0 0         if ($dic_file =~ m!\.hash$!) {
1079             # we have a local dictionary
1080 0           $dic_file =~ s/\.hash/.yaml/;
1081             } else {
1082 0           $dic_file = "$JSPELLLIB/$dic_file.yaml"
1083             }
1084 0           return $dic_file;
1085             }
1086              
1087             sub _mode {
1088 0     0     my $m = shift;
1089 0           my $r="";
1090 0 0         if ($m->{nm}) {
1091 0 0         if ($m->{nm} eq "af") ### af = GPy --> Gym
    0          
    0          
    0          
1092 0           { $r .= "\$G\n\$m\n\$y\n" }
1093             elsif ($m->{nm} eq "full") ### full = GYm
1094 0           { $r .= "\$G\n\$Y\n\$m\n" }
1095             elsif ($m->{nm} eq "cc") ### cc = GPY
1096 0           { $r .= "\$G\n\$P\n\$Y\n" }
1097             elsif ($m->{nm} eq "off") ### off = gPy
1098 0           { $r .= "\$g\n\$P\n\$y\n" }
1099             else {}
1100             }
1101 0 0         if ($m->{flags}) {$r .= "\$z\n"}
  0            
1102 0           else {$r .= "\$Z\n"}
1103 0           return $r;
1104             }
1105              
1106              
1107             sub _irr_file {
1108 0     0     my $irr_file = shift;
1109 0 0         if ($irr_file =~ m!\.hash$!) {
1110             # we have a local dictionary
1111 0           $irr_file =~ s/\.hash/.irr/;
1112             } else {
1113 0           $irr_file = "$JSPELLLIB/$irr_file.irr"
1114             }
1115 0           return $irr_file;
1116             }
1117              
1118              
1119              
1120              
1121             '\o/ yay!'; # End of Lingua::Jspell
1122              
1123             __END__