File Coverage

blib/lib/Lingua/Jspell.pm
Criterion Covered Total %
statement 49 477 10.2
branch 7 278 2.5
condition 0 75 0.0
subroutine 13 46 28.2
pod 21 21 100.0
total 90 897 10.0


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