File Coverage

blib/lib/Lingua/PT/PLNbase.pm
Criterion Covered Total %
statement 254 549 46.2
branch 44 106 41.5
condition 18 46 39.1
subroutine 33 39 84.6
pod 14 14 100.0
total 363 754 48.1


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

    e C.

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