File Coverage

blib/lib/Lingua/PT/PLNbase.pm
Criterion Covered Total %
statement 34 534 6.3
branch 2 102 1.9
condition 2 49 4.0
subroutine 10 35 28.5
pod 14 14 100.0
total 62 734 8.4


line stmt bran cond sub pod time code
1             package Lingua::PT::PLNbase;
2              
3 8     8   190326 use 5.006;
  8         22  
  8         294  
4 8     8   32 use strict;
  8         9  
  8         184  
5 8     8   24 use warnings;
  8         12  
  8         155  
6 8     8   4763 use Data::Dumper;
  8         52746  
  8         1098  
7 8     8   5827 use Lingua::PT::Abbrev;
  8         6605  
  8         400  
8              
9             require Exporter;
10             our @ISA = qw(Exporter);
11              
12 8     8   530 use POSIX qw(locale_h);
  8         5111  
  8         38  
13             my $llang = setlocale(LC_CTYPE, "pt_PT");
14             $llang = setlocale(LC_CTYPE, "pt_BR") unless $llang;
15 8     8   3820 use locale;
  8         777  
  8         32  
16              
17 8     8   4130 use utf8;
  8         70  
  8         31  
18              
19              
20             =encoding UTF-8
21              
22             =head1 NAME
23              
24             Lingua::PT::PLNbase - Perl extension for NLP of the Portuguese
25              
26             =head1 SYNOPSIS
27              
28             use Lingua::PT::PLNbase;
29              
30             my @atomos = atomiza($texto); # tb chamada 'tokenize'
31              
32             my $atomos_um_por_linha = tokeniza($texto);
33             my $atomos_separados_por_virgula = tokeniza({fs => ','}, $texto);
34              
35              
36             my @frases = frases($texto);
37              
38             =head1 DESCRIPTION
39              
40             Este módulo inclui funções básicas úteis ao processamento
41             computacional da língua, e em particular, da língua portuguesa.
42              
43             =cut
44              
45              
46              
47             our @EXPORT = qw(
48             atomiza frases separa_frases fsentences atomos
49             tokeniza has_accents remove_accents
50             xmlsentences sentences
51             cqptokens tokenize
52             );
53              
54             our $VERSION = '0.27';
55              
56             our $abrev;
57              
58             our $terminador = qr{([.?!;]+[\»"'”’]?|<[pP]\b.*?>|
|\n\n+|:\s+(?=[-\«"“‘][A-Z]))};
59              
60             our $protect = qr!
61             \#n\d+
62             | \w+['’]\w+
63             | \bn\.o # number
64             | [\w_.-]+ \@ [\w_.-]+\w # emails
65             | \w+\.?[ºª°]\.? # ordinals
66             | _sec[:+].*?_ # section marks from bookclean
67             | <[A-Za-z](?:\w|:)* #
68             (?:\s+
69             [A-Za-z:0-9]+= # at='v'
70             (?: '[^']+'
71             | "[^"]+")
72             )*
73             \s*/?\s*
74             > # markup open XML SGML
75             | # markup close XML SGML
76             | \d+(?:\/\d+)+ # dates or similar 12/21/1
77             | \d+(?:[.,]\d+)+%? # numbers
78             | \d+(?:\.[oa])+ # ordinals numbers 12.o
79             | (?:\d+\.)+(?=[ ]*[a-z0-9]) # numbers 12. (continuation)
80             | \d+\:\d+(\:\d+)? # the time 12:12:2
81             | (?:\&\w+\;) # entidades XML HTML
82             | ((https?|ftp|gopher)://|www)[\w_./~:-]+\w # urls
83             | \w+\.(?:com|org|net|pt) # simplified urls
84             | \w+(-\w+)+ # dá-lo-à
85             | \\\\unicode\{\d+\} # unicode...
86             | \w+\.(?:exe|html?|zip|jpg|gif|wav|mp3|png|t?gz|pl|xml) # filenames
87             !x;
88              
89              
90             our ($savit_n, %savit_p);
91             our %conf;
92              
93              
94             sub import {
95 7     7   61 my $class = shift;
96 7         13 our %conf = @_;
97 7         1012 $class->export_to_level(1, undef, @EXPORT);
98              
99 7 100 66     46 if ($conf{abbrev} && -f $conf{abbrev}) {
100 1         6 $conf{ABBREV} = Lingua::PT::Abbrev->new($conf{abbrev});
101             } else {
102 6         31 $conf{ABBREV} = Lingua::PT::Abbrev->new();
103             }
104              
105 7         4987 $abrev = $conf{ABBREV}->regexp(nodot=>1);
106             }
107              
108              
109             sub _savit{
110 0     0     my $a=shift;
111 0           $savit_p{++$savit_n}=$a ;
112 0           " __MARCA__$savit_n "
113             }
114              
115             sub _loadit{
116 0     0     my $a = shift;
117 0           $a =~ s/ ?__MARCA__(\d+) ?/$savit_p{$1}/g;
118 0           $savit_n = 0;
119 0           $a;
120             }
121              
122              
123              
124             sub _tokenizecommon {
125 0     0     my $conf = { keep_quotes => 0 };
126 0 0         if (ref($_[0]) eq "HASH") {
127 0           my $c = shift;
128 0           $conf = {%$conf, %$c};
129             }
130              
131 0           my $text = shift;
132              
133 0           for ($text) {
134 0           s/<\?xml.*?\?>//s;
135              
136 0 0         if ($conf->{keep_quotes}) {
137 0           s#\"# \" #g;
138             } else {
139 0           s/^\"/\« /g;
140 0           s/ \"/ \« /g;
141 0           s/\"([ .?!:;,])/ \» $1/g;
142 0           s/\"$/ \»/g;
143             }
144              
145 0           s!(\w)(['’](s|ld|nt|ll|m|t|re))\b!"$1 " . _savit($2)!ge; # I 'm we 're can 't
  0            
146 0           s!([[:alpha:]]+')(\w)! _savit($1) . " $2"!ge;
  0            
147              
148 0 0         if ($conf->{keep_quotes}) {
149 0           s#\'# \' #g;
150             } else {
151 0           s/^\`/\« /g;
152 0           s/ \`/ \« /g;
153              
154 0           s/^\'/\« /g;
155 0           s/ \'/ \« /g;
156 0           s/\'([ .?!:;,])/ \» $1/g;
157 0           s/\'$/ \»/g;
158             }
159              
160 0           s!($protect)! _savit($1)!xge;
  0            
161 0           s!\b((([A-Z])\.)+)!_savit($1)!gie;
  0            
162              
163 0           s!([\»\]])!$1 !g; # » | ]
164 0           s!([\«\[])! $1!g;
165              
166 0           s/(\s*\b\s*|\s+)/\n/g;
167              
168             # s/(.)\n-\n/$1-/g;
169 0           s/\n+/\n/g;
170 0           s/\n(\.?[ºª°])\b/$1/g;
171              
172              
173 0           s#\n($abrev)\n\.\n#\n$1\.\n#ig;
174              
175 0           s#([\]\)])([.,;:!?])#$1\n$2#g;
176              
177 0           s/\n*
178 0           $_ = _loadit($_);
179 0           s/(\s*\n)+$/\n/;
180 0           s/^(\s*\n)+//;
181             }
182             $text
183 0           }
184              
185             =head2 Atomizadores
186              
187             Este módulo inclui um método configurável para a atomização de corpus
188             na língua portuguesa. No entanto, é possível que possa ser usado para
189             outras línguas (especialmente inglês e francês.
190              
191             A forma simples de uso do atomizador é usando directamente a função
192             C que retorna um texto em que cada linha contém um átomo (ou
193             o uso da função C que contém outra versão de atomizador).
194              
195             As funções disponíveis:
196              
197             =over 4
198              
199             =item atomos
200              
201             =item atomiza
202              
203             =item tokenize
204              
205             Usa um algorítmo desenvolvido no Projecto Natura.
206              
207             Para que as aspas não sejam convertidas em I e I
208             aspa>, usar a opção de configuração C.
209              
210             Retorna texto tokenizado, um por linha (a nao ser que o 'record
211             separator' (rs) seja redefenido). Em ambiente lista, retorna a lista
212             dos átomos.
213              
214             my @atomos = atomiza($texto); # tb chamada 'tokenize'
215              
216             my $atomos_um_por_linha = tokeniza($texto);
217             my $atomos_separados_por_virgula = tokeniza({fs => ','}, $texto);
218              
219              
220             =item tokeniza
221              
222             Usa um algoritmo desenvolvido no Pólo de Oslo da Linguateca. Retorna
223             um átomo por linha em contexto escalar, e uma lista de átomos em
224             contexto de lista.
225              
226             =item cqptokens
227              
228             Um átomo por linha de acordo com notação CWB. Pode ser alterado o
229             separador de frases (ou de registo) usando a opção 'irs':
230              
231             cqptokens( { irs => "\n\n" }, "file" );
232              
233             outras opções:
234              
235             cqptokens( { enc => ":utf8"}, "file" ); # enc => charset
236             # outenc => charset
237              
238             =back
239              
240             =cut
241              
242 0     0 1   sub atomos { tokenize(@_) }
243 0     0 1   sub atomiza { tokenize(@_) }
244              
245             sub tokenize{
246 0     0 1   my $conf = { rs => "\n" };
247 0           my $result = "";
248 0           my $text = shift;
249              
250 0 0         if (ref($text) eq "HASH") {
251 0           $conf = { %$conf, %$text };
252 0           $text = shift;
253             }
254              
255 0 0         die __PACKAGE__ . "::tokenize called with undefined value" unless defined $text;
256              
257 0           $result = _tokenizecommon($conf, $text);
258 0           $result =~ s/\n$//g;
259              
260 0 0         if (wantarray) {
261 0           return split /\n+/, $result
262             } else {
263 0 0         $result =~ s/\n/$conf->{rs}/g unless $conf->{rs} eq "\n";
264 0           return $result;
265             }
266             }
267              
268             sub cqptokens{ ##
269 0     0 1   my %opt = ( irs => ">"); # irs => INPUT RECORD SEPARATOR;
270             # enc => charset
271             # outenc => charset
272 0 0         if(ref($_[0]) eq "HASH"){ %opt = (%opt , %{shift(@_)});}
  0            
  0            
273 0   0       my $file = shift || "-";
274              
275 0           local $/ = $opt{irs};
276 0           my %tag=();
277 0           my ($a,$b);
278 0           open(F,"$file");
279 0 0         binmode(F,$opt{enc}) if $opt{enc};
280 0 0         binmode(STDOUT,$opt{outenc}) if $opt{outenc};
281 0           local $_;
282 0           while() {
283 0 0         if(/<(\w+)(.*?)>/){
284 0           ($a, $b) = ($1,$2);
285 0 0         if ($b =~ /=/ ) { $tag{'v'}{$a}++ }
  0            
286 0           else { $tag{'s'}{$a}++ }
287             }
288 0           print _tokenizecommon({},$_)
289             }
290 0           return \%tag
291             }
292              
293              
294              
295             =head2 Segmentadores
296              
297             Este módulo é uma extensão Perl para a segmentação de textos em
298             linguagem natural. O objectivo principal será a possibilidade de
299             segmentação a vários níveis, no entanto esta primeira versão permite
300             apenas a separação em frases (fraseação) usando uma de duas variantes:
301              
302             =over 4
303              
304             =item C
305              
306             =item C
307              
308             @frases = frases($texto);
309              
310             Esta é a implementação do Projecto Natura, que retorna uma lista de
311             frases.
312              
313             =item C
314              
315             $frases = separa_frases($texto);
316              
317             Esta é a implementação da Linguateca, que retorna um texto com uma
318             frase por linha.
319              
320             =item C
321              
322             Utiliza o método C e aplica uma etiqueta XML a cada frase. Por omissão,
323             as frases são ladeadas por '' e ''. O nome da etiqueta pode ser
324             substituído usando o parametro opcional C.
325              
326             xmlsentences({st=> "tag"}, text)
327              
328             =back
329              
330             =cut
331              
332             sub xmlsentences {
333 0     0 1   my %opt = (st => "s") ;
334 0 0         if (ref($_[0]) eq "HASH"){ %opt = (%opt , %{shift(@_)});}
  0            
  0            
335 0           my $par=shift;
336 0           join("\n",map {"<$opt{st}>$_"} (sentences($par)));
  0            
337             }
338              
339              
340              
341 0     0 1   sub frases { sentences(@_) }
342             sub sentences{
343 0     0 1   my @r;
344 0           my $MARCA = "\0x01";
345 0           my $par = shift;
346 0           for ($par) {
347 0           s!($protect)! _savit($1)!xge;
  0            
348 0           s!\b(($abrev)\.)! _savit($1)!ige;
  0            
349 0           s!\b(([A-Z])\.)! _savit($1)!gie; # este à parte para não apanhar minúlculas (s///i)
  0            
350 0           s!($terminador)!$1$MARCA!g;
351 0           $_ = _loadit($_);
352 0           @r = split(/$MARCA/,$_);
353             }
354 0 0 0       if (@r && $r[-1] =~ /^\s*$/s) {
355 0           pop(@r)
356             }
357 0           return map { _trim($_) } @r;
  0            
358             }
359              
360             sub _trim {
361 0     0     my $x = shift;
362 0           $x =~ s/^[\n\r\s]+//;
363 0           $x =~ s/[\n\r\s]+$//;
364 0           return $x;
365             }
366              
367              
368             =head2 Segmentação a vários níveis
369              
370             =over 4
371              
372             =item fsentences
373              
374             A função C permite segmentar um conjunto de ficheiros a
375             vários níveis: por ficheiro, por parágrafo ou por frase. O output pode
376             ser realizado em vários formatos e obtendo, ou não, numeração de
377             segmentos.
378              
379             Esta função é invocada com uma referência para um hash de configuração
380             e uma lista de ficheiros a processar (no caso de a lista ser vazia,
381             irá usar o C).
382              
383             O resultado do processamento é enviado para o C a não ser que
384             a chave C do hash de configuração esteja definida. Nesse caso,
385             o seu valor será usado como ficheiro de resultado.
386              
387             A chave C permite definir o separador de parágrafos. Por
388             omissão, é usada uma linha em branco.
389              
390             A chave C define as políticas de etiquetação do
391             resultado. De momento, a única política disponível é a C.
392              
393             As chaves C, C e C definem as etiquetas a usar,
394             na política XML, para etiquetar frases, parágrafos e textos
395             (ficheiros), respectivamente. Por omissão, as etiquetas usadas são
396             C, C

e C.

397              
398             É possível numerar as etiquetas, definindo as chaves C,
399             C ou C da seguinte forma:
400              
401             =over 4
402              
403             =item '0'
404              
405             Nenhuma numeração.
406              
407             =item 'f'
408              
409             Só pode ser usado com o C, e define que as etiquetas que
410             delimitam ficheiros usará o nome do ficheiro como identificador.
411              
412             =item '1'
413              
414             Numeração a um nível. Cada etiqueta terá um contador diferente.
415              
416             =item '2'
417              
418             Só pode ser usado com o C ou o C e obriga à numeração a
419             dois níveis (N.N).
420              
421             =item '3'
422              
423             Só pode ser usado com o C e obriga à numeração a três níveis (N.N.N)
424              
425             =back
426              
427             =back
428              
429              
430             nomes das etiquetas (s => 's', p=>'p', t=>'text')
431              
432             t: 0 - nenhuma
433             1 - numeracao
434             f - ficheiro [DEFAULT]
435              
436             p: 0 - nenhuma
437             1 - numeracao 1 nivel [DEFAULT]
438             2 - numercao 2 niveis (N.N)
439              
440             s: 0 - nenhuma
441             1 - numeração 1 nível [DEFAULT]
442             2 - numeração 2 níveis (N.N)
443             3 - numeração 3 níveis (N.N.N)
444              
445             =cut
446              
447             sub fsentences {
448 0     0 1   my %opts = (
449             o_format => 'XML',
450             s_tag => 's',
451             s_num => '1',
452             s_last => '',
453              
454             p_tag => 'p',
455             p_num => '1',
456             p_last => '',
457              
458             t_tag => 'text',
459             t_num => 'f',
460             t_last => '',
461              
462             tokenize => 0,
463              
464             output => \*STDOUT,
465             input_p_sep => '',
466             );
467              
468 0 0         %opts = (%opts, %{shift()}) if ref($_[0]) eq "HASH";
  0            
469              
470              
471 0           my @files = @_;
472 0 0         @files = (\*STDIN) unless @files;
473              
474 0           my $oldselect;
475 0 0         if (!ref($opts{output})) {
476 0 0         open OUT, ">$opts{output}" or die("Cannot open file for writting: $!\n");
477 0           $oldselect = select OUT;
478             }
479              
480 0           for my $file (@files) {
481 0           my $fh;
482 0 0         if (ref($file)) {
483 0           $fh = $file;
484             } else {
485 0 0         open $fh, $file or die("Cannot open file $file:$!\n");
486 0           print _open_t_tag(\%opts, $file);
487             }
488              
489 0           my $par;
490 0           local $/ = $opts{input_p_sep};
491 0           while ($par = <$fh>) {
492 0           print _open_p_tag(\%opts);
493              
494 0           chomp($par);
495              
496 0           for my $s (sentences($par)) {
497 0           print _open_s_tag(\%opts), _clean(\%opts,$s), _close_s_tag(\%opts);
498             }
499              
500 0           print _close_p_tag(\%opts);
501             }
502              
503              
504 0 0         unless (ref($file)) {
505 0           print _close_t_tag(\%opts);
506 0           close $fh
507             }
508              
509             }
510              
511 0 0         if (!ref($opts{output})) {
512 0           close OUT;
513 0           select $oldselect;
514             }
515              
516             }
517              
518             sub _clean {
519 0     0     my $opts = shift;
520 0           my $str = shift;
521              
522 0 0         if ($opts->{tokenize}) {
523 0 0         if ($opts->{tokenize} eq "cqp") {
524 0           $str = "\n".join("\n", atomiza($str))."\n"
525             } else {
526 0           $str = join(" ", atomiza($str))
527             }
528             } else {
529 0           $str =~ s/\s+/ /g;
530             }
531 0           $str =~ s/&/&/g;
532 0           $str =~ s/>/>/g;
533 0           $str =~ s/
534 0           return $str;
535             }
536              
537             sub _open_t_tag {
538 0     0     my $opts = shift;
539 0   0       my $file = shift || "";
540 0 0 0       if ($opts->{o_format} eq "XML" &&
541             $opts->{t_tag}) {
542 0 0         if ($opts->{t_num} eq 0) {
    0          
543 0           return "<$opts->{t_tag}>\n";
544             } elsif ($opts->{t_num} eq 'f') {
545 0           $opts->{t_last} = $file;
546 0           $opts->{p_last} = 0;
547 0           $opts->{s_last} = 0;
548 0           return "<$opts->{t_tag} file=\"$file\">\n";
549             } else {
550             ## t_num = 1 :-)
551 0           ++$opts->{t_last};
552 0           $opts->{p_last} = 0;
553 0           $opts->{s_last} = 0;
554 0           return "<$opts->{t_tag} id=\"$opts->{t_last}\">\n";
555             }
556             }
557 0 0         return "" if ($opts->{o_format} eq "NATools");
558             }
559              
560             sub _close_t_tag {
561 0     0     my $opts = shift;
562 0   0       my $file = shift || "";
563 0 0 0       if ($opts->{o_format} eq "XML" &&
564             $opts->{t_tag}) {
565 0           return "{t_tag}>\n";
566             }
567 0 0         return "" if ($opts->{o_format} eq "NATools");
568             }
569              
570             sub _open_p_tag {
571 0     0     my $opts = shift;
572              
573 0 0 0       if ($opts->{o_format} eq "XML" &&
574             $opts->{p_tag}) {
575 0 0         if ($opts->{p_num} == 0) {
    0          
576 0           return "<$opts->{p_tag}>\n";
577             } elsif ($opts->{p_num} == 1) {
578 0           ++$opts->{p_last};
579 0           $opts->{s_last} = 0;
580 0           return "<$opts->{p_tag} id=\"$opts->{p_last}\">\n";
581             } else {
582             ## p_num = 2
583 0           ++$opts->{p_last};
584 0           $opts->{s_last} = 0;
585 0           return "<$opts->{p_tag} id=\"$opts->{t_last}.$opts->{p_last}\">\n";
586             }
587             }
588 0 0         return "" if ($opts->{o_format} eq "NATools");
589             }
590              
591             sub _close_p_tag {
592 0     0     my $opts = shift;
593 0   0       my $file = shift || "";
594 0 0 0       if ($opts->{o_format} eq "XML" &&
595             $opts->{p_tag}) {
596 0           return "{p_tag}>\n";
597             }
598 0 0         return "" if ($opts->{o_format} eq "NATools");
599             }
600              
601              
602             sub _open_s_tag {
603 0     0     my $opts = shift;
604              
605 0 0 0       if ($opts->{o_format} eq "XML" &&
606             $opts->{s_tag}) {
607 0 0         if ($opts->{s_num} == 0) {
    0          
    0          
608 0           return "<$opts->{s_tag}>";
609             } elsif ($opts->{s_num} == 1) {
610 0           ++$opts->{s_last};
611 0           return "<$opts->{s_tag} id=\"$opts->{s_last}\">";
612              
613             } elsif ($opts->{s_num} == 2) {
614 0           ++$opts->{s_last};
615 0           return "<$opts->{s_tag} id=\"$opts->{p_last}.$opts->{s_last}\">";
616              
617             } else {
618             ## p_num = 3
619 0           ++$opts->{s_last};
620 0           return "<$opts->{s_tag} id=\"$opts->{t_last}.$opts->{p_last}.$opts->{s_last}\">";
621             }
622             }
623 0 0         return "" if ($opts->{o_format} eq "NATools");
624             }
625              
626             sub _close_s_tag {
627 0     0     my $opts = shift;
628 0   0       my $file = shift || "";
629 0 0 0       if ($opts->{o_format} eq "XML" &&
630             $opts->{s_tag}) {
631 0           return "{s_tag}>\n";
632             }
633 0 0         return "\n\$\n" if ($opts->{o_format} eq "NATools");
634             }
635              
636              
637              
638              
639              
640             =head2 Acentuação
641              
642             =over 4
643              
644             =item remove_accents
645              
646             Esta função remove a acentuação do texto passado como parâmetro
647              
648             =item has_accents
649              
650             Esta função verifica se o texto passado como parâmetro tem caracteres acentuados
651              
652             =back
653              
654             =cut
655              
656             sub has_accents {
657 0     0 1   my $word = shift;
658 0 0         if ($word =~ m![çáéíóúàèìòùãõâêîôûäëïöüñ]!i) {
659 0           return 1
660             } else {
661 0           return 0
662             }
663             }
664              
665             sub remove_accents {
666 0     0 1   my $word = shift;
667 8     8   26147 $word =~ tr/çáéíóúàèìòùãõâêîôûäëïöüñ/caeiouaeiouaoaeiouaeioun/;
  8         11  
  8         88  
  0            
668 0           $word =~ tr/ÇÁÉÍÓÚÀÈÌÒÙÃÕÂÊÎÔÛÄËÏÖÜÑ/CAEIOUAEIOUAOAEIOUAEIOUN/;
669 0           return $word;
670             }
671              
672              
673              
674              
675              
676             ### ---------- OSLO --------
677              
678             sub tokeniza {
679 0     0 1   my $par = shift;
680              
681 0           for ($par) {
682 0           s/([!?]+)/ $1/g;
683 0           s/([.,;\»´])/ $1/g;
684              
685             # separa os dois pontos só se não entre números 9:30...
686 0           s/:([^0-9])/ :$1/g;
687              
688             # separa os dois pontos só se não entre números e não for http:/...
689 0           s/([^0-9]):([^\/])/$1 :$2/g;
690              
691             # was s/([«`])/$1 /g; -- mas tava a dar problemas com o emacs :|
692 0           s!([`])!$1 !g;
693              
694             # só separa o parêntesis esquerdo quando não engloba números ou asterisco
695 0           s/\(([^1-9*])/\( $1/g;
696              
697             # só separa o parêntesis direito quando não engloba números ou asterisco ou percentagem
698 0           s/([^0-9*%])\)/$1 \)/g;
699              
700             # desfaz a separação dos parênteses para B)
701 0           s/> *([A-Za-z]) \)/> $1\)/g;
702              
703             # desfaz a separação dos parênteses para (a)
704 0           s/> *\( ([a-z]) \)/> \($1\)/g;
705              
706             # separação dos parênteses para ( A4 )
707 0           s/(\( +[A-Z]+[0-9]+)\)/ $1 \)/g;
708              
709             # separa o parêntesis recto esquerdo desde que não [..
710 0           s/\[([^.§])/[ $1/g;
711              
712             # separa o parêntesis recto direito desde que não ..]
713 0           s/([^.§])\]/$1 ]/g;
714              
715             # separa as reticências só se não dentro de [...]
716 0           s/([^[])§/$1 §/g;
717              
718             # desfaz a separação dos http:
719 0           s/http :/http:/g;
720              
721             # separa as aspas anteriores
722 0           s/ \"/ \« /g;
723              
724             # separa as aspas anteriores mesmo no inicio
725 0           s/^\"/ \« /g;
726              
727             # separa as aspas posteriores
728 0           s/\" / \» /g;
729              
730             # separa as aspas posteriores mesmo no fim
731 0           s/\"$/ \»/g;
732              
733             # trata dos apóstrofes
734             # trata do apóstrofe: só separa se for pelica
735 0           s/([^dDlL])\'([\s\',:.?!])/$1 \'$2/g;
736             # trata do apóstrofe: só separa se for pelica
737 0           s/(\S[dDlL])\'([\s\',:.?!])/$1 \'$2/g;
738             # separa d' do resto da palavra "d'amor"... "dest'época"
739 0           s/([A-ZÊÁÉÍÓÚÀÇÔÕÃÂa-zôõçáéíóúâêàã])\'([A-ZÊÁÉÍÓÚÀÇÔÕÃÂa-zôõçáéíóúâêàã])/$1\' $2/;
740              
741             #Para repor PME's
742 0           s/(\s[A-Z]+)\' s([\s,:.?!])/$1\'s$2/g;
743              
744             # isto é para o caso dos apóstrofos não terem sido tratados pelo COMPARA
745             # separa um apóstrofe final usado como inicial
746 0           s/ '([A-Za-zÁÓÚÉÊÀÂÍ])/ ' $1/g;
747             # separa um apóstrofe final usado como inicial
748 0           s/^'([A-Za-zÁÓÚÉÊÀÂÍ])/' $1/g;
749              
750             # isto é para o caso dos apóstrofes (plicas) serem os do COMPARA
751 0           s/\`([^ ])/\` $1/g;
752 0           s/([^ ])´/$1 ´/g;
753              
754             # trata dos (1) ou 1)
755             # separa casos como Rocha(1) para Rocha (1)
756 0           s/([a-záéãó])\(([0-9])/$1 \($2/g;
757             # separa casos como dupla finalidade:1)
758 0           s/:([0-9]\))/ : $1/g;
759              
760             # trata dos hífenes
761             # separa casos como (Itália)-Juventus para Itália) -
762 0           s/\)\-([A-Z])/\) - $1/g;
763             # separa casos como 1-universidade
764 0           s/([0-9]\-)([^0-9\s])/$1 $2/g;
765             }
766              
767             #trata das barras
768             #se houver palavras que nao sao todas em maiusculas, separa
769 0           my @barras = ($par=~m%(?:[a-z]+/)+(?:[A-Za-z][a-z]*)%g);
770 0           my $exp_antiga;
771 0           foreach my $exp_com_barras (@barras) {
772 0 0 0       if (($exp_com_barras !~ /[a-z]+a\/o$/) and # Ambicioso/a
      0        
773             ($exp_com_barras !~ /[a-z]+o\/a$/) and # cozinheira/o
774             ($exp_com_barras !~ /[a-z]+r\/a$/)) { # desenhador/a
775 0           $exp_antiga=$exp_com_barras;
776 0           $exp_com_barras=~s#/# / #g;
777 0           $par=~s/$exp_antiga/$exp_com_barras/g;
778             }
779             }
780              
781 0           for ($par) {
782 0           s# e / ou # e/ou #g;
783 0           s#([Kk])m / h#$1m/h#g;
784 0           s# mg / kg# mg/kg#g;
785 0           s#r / c#r/c#g;
786 0           s#m / f#m/f#g;
787 0           s#f / m#f/m#g;
788             }
789              
790              
791 0 0         if (wantarray) {
792 0           return split /\s+/, $par
793             } else {
794 0           $par =~ s/\s+/\n/g;
795 0           return $par
796             }
797             }
798              
799              
800              
801             sub tratar_pontuacao_interna {
802 0     0 1   my $par = shift;
803              
804             # print "Estou no pontuação interna... $par\n";
805              
806 0           for ($par) {
807             # proteger o §
808 0           s/§/§§/g;
809              
810             # tratar das reticências
811 0           s/\.\.\.+/§/g;
812              
813 0           s/\+/\+\+/g;
814              
815             # tratar de iniciais seguidas por ponto, eventualmente com
816             # parênteses, no fim de uma frase
817 0           s/([A-Z])\. ([A-Z])\.(\s*[])]*\s*)$/$1+ $2+$3 /g;
818              
819             # iniciais com espaço no meio...
820 0           s/ a\. C\./ a+C+/g;
821 0           s/ d\. C\./ d+C+/g;
822              
823             # tratar dos pontos nas abreviaturas
824 0           s/\.º/º+/g;
825 0           s/º\./+º/g;
826 0           s/\.ª/+ª/g;
827 0           s/ª\./ª+/g;
828              
829             #só mudar se não for ambíguo com ponto final
830 0           s/º\. +([^A-ZÀÁÉÍÓÚÂÊ\«])/º+ $1/g;
831              
832             # formas de tratamento
833 0           s/Ex\./Ex+/g; # Ex.
834 0           s/ ex\./ ex+/g; # ex.
835 0           s/Exa(s*)\./Exa$1+/g; # Exa., Exas.
836 0           s/ exa(s*)\./ exa$1+/g; # exa., exas
837 0           s/Pe\./Pe+/g;
838 0           s/Dr(a*)\./Dr$1+/g; # Dr., Dra.
839 0           s/ dr(a*)\./ dr$1+/g; # dr., dra.
840 0           s/ drs\./ drs+/g; # drs.
841 0           s/Eng(a*)\./Eng$1+/g; # Eng., Enga.
842 0           s/ eng(a*)\./ eng$1+/g; # eng., enga.
843 0           s/([Ss])r(t*)a\./$1r$2a+/g; # Sra., sra., Srta., srta.
844 0           s/([Ss])r(s*)\./$1r$2+/g; # Sr., sr., Srs., srs.
845 0           s/ arq\./ arq+/g; # arq.
846 0           s/Prof(s*)\./Prof$1+/g; # Prof., Profs.
847 0           s/Profa(s*)\./Profa$1+/g; # Profa., Profas.
848 0           s/ prof(s*)\./ prof$1+/g; # prof., profs.
849 0           s/ profa(s*)\./ profa$1+/g; # profa., profas.
850 0           s/\. Sen\./+ Sen+/g; # senador (vem sempre depois de Av. ou R. ...)
851 0           s/ua Sen\./ua Sen+/g; # senador (depois [Rr]ua ...)
852 0           s/Cel\./Cel+/g; # coronel
853 0           s/ d\. / d+ /g; # d. Luciano
854              
855             # partes de nomes (pospostos)
856 0           s/ ([lL])da\./ $1da+/g; # limitada
857 0           s/ cia\./ cia+/g; # companhia
858 0           s/Cia\./Cia+/g; # companhia
859 0           s/Jr\./Jr+/g;
860              
861             # moradas
862 0           s/Av\./Av+/g;
863 0           s/ av\./ av+/g;
864 0           s/Est(r*)\./Est$1+/g;
865 0           s/Lg(o*)\./Lg$1+/g;
866 0           s/ lg(o*)\./ lg$1+/g;
867 0           s/T(ra)*v\./T$1v+/g; # Trav., Tv.
868 0           s/([^N])Pq\./$1Pq+/g; # Parque (cuidado com CNPq)
869 0           s/ pq\./ pq+/g; # parque
870 0           s/Jd\./Jd+/g; # jardim
871 0           s/Ft\./Ft+/g; # forte
872 0           s/Cj\./Cj+/g; # conjunto
873 0           s/ ([lc])j\./ $1j+/g; # conjunto ou loja
874             # $par=~s/ al\./ al+/g; # alameda tem que ir para depois de et.al...
875              
876             # Remover aqui uns warningzitos
877 0           s/Tel\./Tel+/g; # Tel.
878 0           s/Tel(e[fm])\./Tel$1+/g; # Telef., Telem.
879 0           s/ tel\./ tel+/g; # tel.
880 0           s/ tel(e[fm])\./ tel$1+/g; # telef., telem.
881 0           s/Fax\./Fax+/g; # Fax.
882 0           s/ cx\./ cx+/g; # caixa
883              
884             # abreviaturas greco-latinas
885 0           s/ a\.C\./ a+C+/g;
886 0           s/ a\.c\./ a+c+/g;
887 0           s/ d\.C\./ d+C+/g;
888 0           s/ d\.c\./ d+c+/g;
889 0           s/ ca\./ ca+/g;
890 0           s/etc\.([.,;])/etc+$1/g;
891 0           s/etc\.\)([.,;])/etc+)$1/g;
892 0           s/etc\. --( *[a-záéíóúâêà,])/etc+ --$1/g;
893 0           s/etc\.(\)*) ([^A-ZÀÁÉÍÓÂÊ])/etc+$1 $2/g;
894 0           s/ et\. *al\./ et+al+/g;
895 0           s/ al\./ al+/g; # alameda
896 0           s/ q\.b\./ q+b+/g;
897 0           s/ i\.e\./ i+e+/g;
898 0           s/ibid\./ibid+/g;
899 0           s/ id\./ id+/g; # se calhar é preciso ver se não vem sempre precedido de um (
900 0           s/op\.( )*cit\./op+$1cit+/g;
901 0           s/P\.S\./P+S+/g;
902              
903             # unidades de medida
904 0           s/([0-9][hm])\. ([^A-ZÀÁÉÍÓÚÂÊ])/$1+ $2/g; # 19h., 24m.
905 0           s/([0-9][km]m)\. ([^A-ZÀÁÉÍÓÚÂÊ])/$1+ $2/g; # 20km., 24mm.
906 0           s/([0-9]kms)\. ([^A-ZÀÁÉÍÓÚÂÊ])/$1+ $2/g; # kms. !!
907 0           s/(\bm)\./$1+/g; # metros no MINHO
908              
909             # outros
910 0           s/\(([Oo]rgs*)\.\)/($1+)/g; # (orgs.)
911 0           s/\(([Ee]ds*)\.\)/($1+)/g; # (eds.)
912 0           s/séc\./séc+/g;
913 0           s/pág(s*)\./pág$1+/g;
914 0           s/pg\./pg+/g;
915 0           s/pag\./pag+/g;
916 0           s/ ed\./ ed+/g;
917 0           s/Ed\./Ed+/g;
918 0           s/ sáb\./ sáb+/g;
919 0           s/ dom\./ dom+/g;
920 0           s/ id\./ id+/g;
921 0           s/ min\./ min+/g;
922 0           s/ n\.o(s*) / n+o$1 /g; # abreviatura de numero no MLCC-DEB
923 0           s/ ([Nn])o\.(s*)\s*([0-9])/ $1o+$2 $3/g; # abreviatura de numero no., No.
924 0           s/ n\.(s*)\s*([0-9])/ n+$1 $2/g; # abreviatura de numero n. no ANCIB
925 0           s/ num\. *([0-9])/ num+ $1/g; # abreviatura de numero num. no ANCIB
926 0           s/ c\. ([0-9])/ c+ $1/g; # c. 1830
927 0           s/ p\.ex\./ p+ex+/g;
928 0           s/ p\./ p+/g;
929 0           s/ pp\./ pp+/g;
930 0           s/ art(s*)\./ art$1+/g;
931 0           s/Min\./Min+/g;
932 0           s/Inst\./Inst+/g;
933 0           s/vol(s*)\./vol$1+ /g;
934 0           s/ v\. *([0-9])/ v+ $1/g; # abreviatura de volume no ANCIB
935 0           s/\(v\. *([0-9])/\(v+ $1/g; # abreviatura de volume no ANCIB
936 0           s/^v\. *([0-9])/v+ $1/g; # abreviatura de volume no ANCIB
937 0           s/Obs\./Obs+/g;
938              
939             # Abreviaturas de meses
940 0           s/(\W)jan\./$1jan+/g;
941 0           s/\Wfev\./$1fev+/g;
942 0           s/(\/\s*)mar\.(\s*[0-9\/])/$1mar+$2/g; # a palavra "mar"
943 0           s/(\W)mar\.(\s*[0-9]+)/$1mar\+$2/g;
944 0           s/(\W)abr\./$1abr+/g;
945 0           s/(\W)mai\./$1mai+/g;
946 0           s/(\W)jun\./$1jun+/g;
947 0           s/(\W)jul\./$1jul+/g;
948 0           s/(\/\s*)ago\.(\s*[0-9\/])/$1ago+$2/g; # a palavra inglesa "ago"
949 0           s/ ago\.(\s*[0-9\/])/ ago+$1/g; # a palavra inglesa "ago./"
950 0           s/(\W)set\.(\s*[0-9\/])/$1set+$2/g; # a palavra inglesa "set"
951 0           s/([ \/])out\.(\s*[0-9\/])/$1out+$2/g; # a palavra inglesa "out"
952 0           s/(\W)nov\./$1nov+/g;
953 0           s/(\/\s*)dez\.(\s*[0-9\/])/$1dez+$2/g; # a palavra "dez"
954 0           s/(\/\s*)dez\./$1dez+/g; # a palavra "/dez."
955              
956             # Abreviaturas inglesas
957 0           s/Bros\./Bros+/g;
958 0           s/Co\. /Co+ /g;
959 0           s/Co\.$/Co+/g;
960 0           s/Com\. /Com+ /g;
961 0           s/Com\.$/Com+/g;
962 0           s/Corp\. /Corp+ /g;
963 0           s/Inc\. /Inc+ /g;
964 0           s/Ltd\. /Ltd+ /g;
965 0           s/([Mm])r(s*)\. /$1r$2+ /g;
966 0           s/Ph\.D\./Ph+D+/g;
967 0           s/St\. /St+ /g;
968 0           s/ st\. / st+ /g;
969              
970             # Abreviaturas francesas
971 0           s/Mme\./Mme+/g;
972              
973             # Abreviaturas especiais do Diário do Minho
974 0           s/ habilit\./ habilit+/g;
975 0           s/Hab\./Hab+/g;
976 0           s/Mot\./Mot+/g;
977 0           s/\-Ang\./-Ang+/g;
978 0           s/(\bSp)\./$1+/g; # Sporting
979 0           s/(\bUn)\./$1+/g; # Universidade
980              
981             # Abreviaturas especiais do Folha
982 0           s/([^'])Or\./$1Or+/g; # alemanha Oriental, evitar d'Or
983 0           s/Oc\./Oc+/g; # alemanha Ocidental
984              
985             }
986              
987             # tratar dos conjuntos de iniciais
988 0           my @siglas_iniciais = ($par =~ /^(?:[A-Z]\. *)+[A-Z]\./);
989 0           my @siglas_finais = ($par =~ /(?:[A-Z]\. *)+[A-Z]\.$/);
990 0           my @siglas = ($par =~ m#(?:[A-Z]\. *)+(?:[A-Z]\.)(?=[]\)\s,;:!?/])#g); #trata de conjuntos de iniciais
991 0           push (@siglas, @siglas_iniciais);
992 0           push (@siglas, @siglas_finais);
993 0           my $sigla_antiga;
994 0           foreach my $sigla (@siglas) {
995 0           $sigla_antiga = $sigla;
996 0           $sigla =~ s/\./+/g;
997 0           $sigla_antiga =~ s/\./\\\./g;
998             # print "SIGLA antes: $sigla, $sigla_antiga\n";
999 0           $par =~ s/$sigla_antiga/$sigla/g;
1000             # print "SIGLA: $sigla\n";
1001             }
1002              
1003             # tratar de pares de iniciais ligadas por hífen (à francesa: A.-F.)
1004 0           for ($par) {
1005 0           s/ ([A-Z])\.\-([A-Z])\. / $1+-$2+ /g;
1006             # tratar de iniciais (únicas?) seguidas por ponto
1007 0           s/ ([A-Z])\. / $1+ /g;
1008             # tratar de iniciais seguidas por ponto
1009 0           s/^([A-Z])\. /$1+ /g;
1010             # tratar de iniciais seguidas por ponto antes de aspas "D. João
1011             # VI: Um Rei Aclamado"
1012 0           s/([("\«])([A-Z])\. /$1$2+ /g;
1013             }
1014              
1015             # Tratar dos URLs (e também dos endereços de email)
1016             # email= url@url...
1017             # aceito endereços seguidos de /hgdha/hdga.html
1018             # seguidos de /~hgdha/hdga.html
1019             # @urls=($par=~/(?:[a-z][a-z0-9-]*\.)+(?:[a-z]+)(?:\/~*[a-z0-9-]+)*?(?:\/~*[a-z0-9][a-z0-9.-]+)*(?:\/[a-z.]+\?[a-z]+=[a-z0-9-]+(?:\&[a-z]+=[a-z0-9-]+)*)*/gi);
1020              
1021 0           my @urls = ($par =~ /(?:[a-z][a-z0-9-]*\.)+(?:[a-z]+)(?:\/~*[a-z0-9][a-z0-9.-]+)*(?:\?[a-z]+=[a-z0-9-]+(?:\&[a-z]+=[a-z0-9-]+)*)*/gi);
1022 0           my $url_antigo;
1023 0           foreach my $url (@urls) {
1024 0           $url_antigo = $url;
1025 0           $url_antigo =~ s/\./\\./g; # para impedir a substituição de P.o em vez de P\.o
1026 0           $url_antigo =~ s/\?/\\?/g;
1027 0           $url =~ s/\./+/g;
1028             # Se o último ponto está mesmo no fim, não faz parte do URL
1029 0           $url =~ s/\+$/./;
1030 0           $url =~ s/\//\/\/\/\//g; # põe quatro ////
1031 0           $par =~ s/$url_antigo/$url/;
1032             }
1033             # print "Depois de tratar dos URLs: $par\n";
1034              
1035 0           for ($par) {
1036             # de qualquer maneira, se for um ponto seguido de uma vírgula, é
1037             # abreviatura...
1038 0           s/\. *,/+,/g;
1039             # de qualquer maneira, se for um ponto seguido de outro ponto, é
1040             # abreviatura...
1041 0           s/\. *\./+./g;
1042              
1043             # tratamento de numerais
1044 0           s/([0-9]+)\.([0-9]+)\.([0-9]+)/$1_$2_$3/g;
1045 0           s/([0-9]+)\.([0-9]+)/$1_$2/g;
1046              
1047             # tratamento de numerais cardinais
1048             # - tratar dos números com ponto no início da frase
1049 0           s/^([0-9]+)\. /$1+ /g;
1050             # - tratar dos números com ponto antes de minúsculas
1051 0           s/([0-9]+)\. ([a-záéíóúâêà])/$1+ $2/g;
1052              
1053             # tratamento de numerais ordinais acabados em .o
1054 0           s/([0-9]+)\.([oa]s*) /$1+$2 /g;
1055             # ou expressos como 9a.
1056 0           s/([0-9]+)([oa]s*)\. /$1$2+ /g;
1057              
1058             # tratar numeracao decimal em portugues
1059 0           s/([0-9]),([0-9])/$1#$2/g;
1060              
1061             #print "TRATA: $par\n";
1062              
1063             # tratar indicação de horas
1064             # esta é tratada na tokenização - não separando 9:20 em 9 :20
1065             }
1066 0           return $par;
1067             }
1068              
1069              
1070             sub separa_frases {
1071 0     0 1   my $par = shift;
1072              
1073             # $num++;
1074              
1075 0           $par = &tratar_pontuacao_interna($par);
1076              
1077             # print "Depois de tratar_pontuacao_interna: $par\n";
1078              
1079 0           for ($par) {
1080              
1081             # primeiro junto os ) e os -- ao caracter anterior de pontuação
1082 0           s/([?!.])\s+\)/$1\)/g; # pôr "ola? )" para "ola?)"
1083 0           s/([?!.])\s+\-/$1-/g; # pôr "ola? --" para "ola?--"
1084 0           s/([?!.])\s+§/$1§/g; # pôr "ola? ..." para "ola?..."
1085 0           s/§\s+\-/$1-/g; # pôr "ola§ --" para "ola§--"
1086              
1087             # junto tb o travessão -- `a pelica '
1088 0           s/\-\- \' *$/\-\-\' /;
1089              
1090             # separar esta pontuação, apenas se não for dentro de aspas, ou
1091             # seguida por vírgulas ou parênteses o a-z estáo lá para não
1092             # separar /asp?id=por ...
1093 0           s/([?!]+)([^-\»'´,§?!)"a-z])/$1.$2/g;
1094              
1095             # Deixa-se o travessão para depois
1096             # print "Depois de tratar do ?!: $par";
1097              
1098             # separar as reticências entre parênteses apenas se forem seguidas
1099             # de nova frase, e se não começarem uma frase elas próprias
1100 0           s/([\w?!])§([\»"´']*\)) *([A-ZÁÉÍÓÚÀ])/$1§$2.$3/g;
1101              
1102             # print "Depois de tratar das retic. seguidas de ): $par";
1103              
1104             # separar os pontos antes de parênteses se forem seguidos de nova
1105             # frase
1106 0           s/([\w])\.([)]) *([A-ZÁÉÍÓÚÀ])/$1 + $2.$3/g;
1107              
1108             # separar os pontos ? e ! antes de parênteses se forem seguidos de
1109             # nova frase, possivelmente tb iniciada por abre parênteses ou
1110             # travessão
1111 0           s/(\w[?!]+)([)]) *((?:\( |\-\- )*[A-ZÁÉÍÓÚÀ])/$1 $2.$3/g;
1112              
1113             # separar as reticências apenas se forem seguidas de nova frase, e
1114             # se não começarem uma frase elas próprias trata também das
1115             # reticências antes de aspas
1116 0           s/([\w\d!?])\s*§(["\»'´]*) ([^\»"'a-záéíóúâêàäëïöü,;?!)])/$1§$2.$3/g;
1117 0           s/([\w\d!?])\s*§(["\»'´]*)\s*$/$1§$2. /g;
1118              
1119             # aqui trata das frases acabadas por aspas, eventualmente tb
1120             # fechando parênteses e seguidas por reticências
1121 0           s/([\w!?]["\»'´])§(\)*) ([^\»"a-záéíóúâêàäëïöü,;?!)])/$1§$2.$3/g;
1122              
1123             #print "depois de tratar das reticencias seguidas de nova frase: $par\n";
1124              
1125             # tratar dos dois pontos: apenas se seguido por discurso directo
1126             # em maiúsculas
1127 0           s/: \«([A-ZÁÉÍÓÚÀ])/:.\«$1/g;
1128 0           s/: (\-\-[ \«]*[A-ZÁÉÍÓÚÀ])/:.$1/g;
1129              
1130             # tratar dos dois pontos se eles acabam o parágrafo (é preciso pôr
1131             # um espaço)
1132 0           s/:\s*$/:. /;
1133              
1134             # tratar dos pontos antes de aspas
1135 0           s/\.(["\»'´])([^.])/+$1.$2/g;
1136              
1137             # tratar das aspas quando seguidas de novas aspas
1138 0           s/\»\s*[\«"]/\». \«/g;
1139              
1140             # tratar de ? e ! seguidos de aspas quando seguidos de maiúscula
1141             # eventualmente iniciados por abre parênteses ou por travessão
1142 0           s/([?!])([\»"'´]) ((?:\( |\-\- )*[A-ZÁÉÍÓÚÀÊÂ])/$1$2. $3/g;
1143              
1144             # separar os pontos ? e ! antes de parênteses e possivelmente
1145             # aspas se forem o fim do parágrafo
1146 0           s/(\w[?!]+)([)][\»"'´]*) *$/$1 $2./;
1147              
1148             # tratar dos pontos antes de aspas precisamente no fim
1149 0           s/\.([\»"'´])\s*$/+$1. /g;
1150              
1151             # tratar das reticências e outra pontuação antes de aspas ou
1152             # plicas precisamente no fim
1153 0           s/([!?§])([\»"'´]+)\s*$/$1$2. /g;
1154              
1155             #tratar das reticências precisamente no fim
1156 0           s/§\s*$/§. /g;
1157              
1158             # tratar dos pontos antes de parêntesis precisamente no fim
1159 0           s/\.\)\s*$/+\). /g;
1160              
1161             # aqui troco .) por .). ...
1162 0           s/\.\)\s/+\). /g;
1163             }
1164              
1165             # tratar de parágrafos que acabam em letras, números, vírgula ou
1166             # "-", chamando-os fragmentos #ALTERACAO
1167 0           my $fragmento;
1168 0 0         if ($par =~/[A-Za-záéíóúêãÁÉÍÓÚÀ0-9\),-][\»\"\'´>]*\s*\)*\s*$/) {
1169 0           $fragmento = 1
1170             }
1171              
1172 0           for ($par) {
1173             # se o parágrafo acaba em "+", deve-se juntar "." outra vez.
1174 0           s/([^+])\+\s*$/$1+. /;
1175              
1176             # se o parágrafo acaba em abreviatura (+) seguido de aspas ou parêntesis, deve-se juntar "."
1177 0           s/([^+])\+\s*(["\»'´\)])\s*$/$1+$2. /;
1178              
1179             # print "Parágrafo antes da separação: $par";
1180             }
1181              
1182 0           my @sentences = split /\./,$par;
1183 0 0 0       if (($#sentences > 0) and not $fragmento) {
1184 0           pop(@sentences);
1185             }
1186              
1187 0           my $resultado = "";
1188             # para saber em que frase pôr
1189 0           my $num_frase_no_paragrafo = 0;
1190 0           foreach my $frase (@sentences) {
1191 0           $frase = &recupera_ortografia_certa($frase);
1192              
1193 0 0 0       if (($frase=~/[.?!:;][\»"'´]*\s*$/) or
    0 0        
1194             ($frase=~/[.?!] *\)[\»"'´]*$/)) {
1195             # frase normal acabada por pontuação
1196 0           $resultado .= " $frase \n";
1197             }
1198              
1199             elsif (($fragmento) and ($num_frase_no_paragrafo == $#sentences)) {
1200 0           $resultado .= " $frase \n";
1201 0           $fragmento = 0;
1202             }
1203             else {
1204 0           $resultado .= " $frase . \n";
1205             }
1206 0           $num_frase_no_paragrafo++;
1207             }
1208              
1209 0           return $resultado;
1210             }
1211              
1212              
1213             sub recupera_ortografia_certa {
1214             # os sinais literais de + são codificados como "++" para evitar
1215             # transformação no ponto, que é o significado do "+"
1216              
1217 0     0 1   my $par = shift;
1218              
1219 0           for ($par) {
1220 0           s/([^+])\+(?!\+)/$1./g; # um + não seguido por +
1221 0           s/\+\+/+/g;
1222 0           s/^§(?!§)/.../g; # se as reticências começam a frase
1223 0           s/([^§(])§(?!§)\)/$1... \)/g; # porque se juntou no separa_frases
1224             # So nao se faz se for (...) ...
1225 0           s/([^§])§(?!§)/$1.../g; # um § não seguido por §
1226 0           s/§§/§/g;
1227 0           s/_/./g;
1228 0           s/#/,/g;
1229 0           s#////#/#g; #passa 4 para 1
1230 0           s/([?!])\-/$1 \-/g; # porque se juntou no separa_frases
1231 0           s/([?!])\)/$1 \)/g; # porque se juntou no separa_frases
1232             }
1233 0           return $par;
1234             }
1235              
1236              
1237             1;
1238             __END__