File Coverage

blib/lib/WWW/Dict/Leo/Org.pm
Criterion Covered Total %
statement 27 157 17.2
branch 0 44 0.0
condition 0 6 0.0
subroutine 9 17 52.9
pod 4 8 50.0
total 40 232 17.2


line stmt bran cond sub pod time code
1             #
2             # Copyleft (l) 2000-2017 Thomas v.D. .
3             #
4             # leo may be
5             # used and distributed under the terms of the GNU General Public License.
6             # All other brand and product names are trademarks, registered trademarks
7             # or service marks of their respective holders.
8              
9             package WWW::Dict::Leo::Org;
10             $WWW::Dict::Leo::Org::VERSION = "2.00";
11              
12 1     1   15697 use strict;
  1         2  
  1         34  
13 1     1   4 use warnings;
  1         1  
  1         34  
14 1     1   735 use English '-no_match_vars';
  1         3843  
  1         6  
15 1     1   1163 use Carp::Heavy;
  1         162  
  1         42  
16 1     1   6 use Carp;
  1         1  
  1         78  
17 1     1   559 use IO::Socket;
  1         22121  
  1         4  
18 1     1   987 use MIME::Base64;
  1         677  
  1         50  
19 1     1   834 use XML::Simple;
  1         8154  
  1         14  
20 1     1   1099 use Encode;
  1         10249  
  1         2238  
21              
22             sub debug;
23              
24             sub new {
25 0     0 0   my ($class, %param) = @_;
26 0   0       my $type = ref( $class ) || $class;
27              
28 0           my %settings = (
29             "-Host" => "dict.leo.org",
30             "-Port" => 80,
31             "-UserAgent" => "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0",
32             "-Proxy" => "",
33             "-ProxyUser" => "",
34             "-ProxyPass" => "",
35             "-Debug" => 0,
36             "-Language" => "en", # en2de, de2fr, fr2de, de2es, es2de
37             "data" => {}, # the results
38             "section" => [],
39             "title" => "",
40             "segments" => [],
41             "Maxsize" => 0,
42             "Linecount" => 0,
43             );
44              
45 0           foreach my $key (keys %param) {
46 0           $settings{$key} = $param{$key}; # override defaults
47             }
48              
49 0           my $self = \%settings;
50 0           bless $self, $type;
51              
52 0           return $self;
53             }
54              
55             sub translate {
56 0     0 1   my($this, $term) = @_;
57              
58 0 0         if (! $term) {
59 0           croak "No term to translate given!";
60             }
61              
62 0           my $linecount = 0;
63 0           my $maxsize = 0;
64 0           my @match = ();
65              
66             #
67             # form var transitions for searchLoc(=translation direction) and lp(=language)
68 0           my %lang = ( speak => "ende" );
69              
70 0           my @langs = qw(en es ru pt fr pl ch it);
71 0 0         if ($this->{"-Language"}) {
72             # en | fr | ru2en | de2pl etc
73             # de2, 2de, de are not part of lang spec
74 0 0         if (! grep { $this->{"-Language"} =~ /$_/ } @langs) {
  0            
75 0           croak "Unsupported language: " . $this->{"-Language"};
76             }
77 0           my $spec = $this->{"-Language"};
78 0           my $l;
79 0 0         if ($spec =~ /(..)2de/) {
    0          
80 0           $l = $1;
81 0           $this->{"-Language"} = -1;
82 0           $lang{speak} = "${l}de";
83             }
84             elsif ($spec =~ /de2(..)/) {
85 0           $l = $1;
86 0           $this->{"-Language"} = 1;
87 0           $lang{speak} = "${l}de";
88             }
89             else {
90 0           $lang{speak} = $this->{"-Language"} . 'de';
91 0           $this->{"-Language"} = 0;
92             }
93             }
94              
95             # add language
96 0           my @form;
97 0           push @form, "lp=$lang{speak}";
98              
99             #
100             # process whitespaces
101             #
102 0           my $query = $term;
103 0           $query =~ s/\s\s*/ /g;
104 0           $query =~ s/\s/\+/g;
105 0           push @form, "search=$query";
106              
107             #
108             # make the query cgi'ish
109             #
110 0           my $form = join "&", @form;
111              
112             # store for result caching
113 0           $this->{Form} = $form;
114              
115             #
116             # check for proxy settings and use it if exists
117             # otherwise use direct connection
118             #
119 0           my ($url, $site);
120 0           my $ip = $this->{"-Host"};
121 0           my $port = $this->{"-Port"};
122 0           my $proxy_user = $this->{"-ProxyUser"};
123 0           my $proxy_pass = $this->{"-ProxyPass"};
124              
125 0 0         if ($this->{"-Proxy"}) {
126 0           my $proxy = $this->{"-Proxy"};
127 0           $proxy =~ s/^http:\/\///i;
128 0 0         if ($proxy =~ /^(.+):(.+)\@(.*)$/) {
129             # proxy user account
130 0           $proxy_user = $1;
131 0           $proxy_pass = $2;
132 0           $proxy = $3;
133 0           $this->debug( "proxy_user: $proxy_user");
134             }
135 0           my($host, $pport) = split /:/, $proxy;
136 0 0         if ($pport) {
137 0           $url = "http://$ip:$port/dictQuery/m-vocab/$lang{speak}/query.xml";
138 0           $port = $pport;
139             }
140             else {
141 0           $port = 80;
142             }
143 0           $ip = $host;
144 0           $this->debug( "connecting to proxy:", $ip, $port);
145             }
146             else {
147 0           $this->debug( "connecting to site:", $ip, "port", $port);
148 0           $url = "/dictQuery/m-vocab/$lang{speak}/query.xml";
149             }
150              
151 0 0         my $conn = new IO::Socket::INET(
152             Proto => "tcp",
153             PeerAddr => $ip,
154             PeerPort => $port,
155             ) or die "Unable to connect to $ip:$port: $!\n";
156 0           $conn->autoflush(1);
157              
158 0           $this->debug( "GET $url?$form HTTP/1.0");
159 0           print $conn "GET $url?$form HTTP/1.0\r\n";
160              
161             # be nice, simulate Konqueror.
162 0           print $conn
163             qq($this->{"-UserAgent"}
164             Host: $this->{"-Host"}:$this->{"-Port"}
165             Accept: text/*;q=1.0, image/png;q=1.0, image/jpeg;q=1.0, image/gif;q=1.0, image/*;q=0.8, */*;q=0.5
166             Accept-Charset: iso-8859-1;q=1.0, *;q=0.9, utf-8;q=0.8
167             Accept-Language: en_US, en\r\n);
168              
169 0 0 0       if ($this->{"-Proxy"} and $proxy_user) {
170             # authenticate
171             # construct the auth header
172 0           my $coded = encode_base64("$proxy_user:$proxy_pass");
173 0           $this->debug( "Proxy-Authorization: Basic $coded");
174 0           print $conn "Proxy-Authorization: Basic $coded\r\n";
175             }
176              
177             # finish the request
178 0           print $conn "\r\n";
179              
180             #
181             # parse dict.leo.org output
182             #
183 0           $site = "";
184 0           my $got_headers = 0;
185 0           while (<$conn>) {
186 0 0         if ($got_headers) {
    0          
    0          
187 0           $site .= $_;
188             }
189             elsif (/^\r?$/) {
190 0           $got_headers = 1;
191             }
192             elsif ($_ !~ /HTTP\/1\.(0|1) 200 OK/i) {
193 0 0         if (/HTTP\/1\.(0|1) (\d+) /i) {
194             # got HTTP error
195 0           my $err = $2;
196 0 0         if ($err == 407) {
197 0           croak "proxy auth required or access denied!\n";
198 0           close $conn;
199 0           return ();
200             }
201             else {
202 0           croak "got HTTP error $err!\n";
203 0           close $conn;
204 0           return ();
205             }
206             }
207             }
208             }
209              
210 0 0         close $conn or die "Connection failed: $!\n";
211 0           $this->debug( "connection: done");
212              
213 0           $this->{Linecount} = 0;
214 0           $this->{Maxsize} = 0;
215              
216             # parse the XML
217 0           my $xml = new XML::Simple;
218 0           my $data = $xml->XMLin($site,
219             ForceArray => [ 'section', 'entry' ],
220             ForceContent => 1,
221             KeyAttr => { side => 'lang' }
222             );
223              
224 0           my (@matches, $from_lang, $to_lang);
225 0           $from_lang = substr $lang{speak}, 0, 2;
226 0           $to_lang = substr $lang{speak}, 2, 2;
227              
228 0           foreach my $section (@{$data->{sectionlist}->{section}}) {
  0            
229 0           my @entries;
230 0           foreach my $entry (@{$section->{entry}}) {
  0            
231              
232 0           my $left = $this->parse_word($entry->{side}->{$from_lang}->{words}->{word});
233 0           my $right = $this->parse_word($entry->{side}->{$to_lang}->{words}->{word});
234              
235 0           push @entries, { left => $left, right => $right };
236 0 0         if ($this->{Maxsize} < length($left)) {
237 0           $this->{Maxsize} = length($left);
238             }
239 0           $this->{Linecount}++;
240             }
241             push @matches, {
242 0           title => encode('UTF-8', $section->{sctTitle}),
243             data => \@entries
244             };
245             }
246              
247 0           return @matches;
248             }
249              
250             # parse all the s and build a string
251             sub parse_word {
252 0     0 0   my ($this, $word) = @_;
253 0 0         if (ref $word eq "HASH") {
    0          
254 0 0         if ($word->{content}) {
    0          
255 0           return encode('UTF-8', $word->{content});
256             }
257             elsif ($word->{cc}) {
258             # chinese simplified, traditional and pinyin
259             return encode('UTF-8', $word->{cc}->{cs}->{content} . "[" .
260             $word->{cc}->{ct}->{content} . "] " .
261 0           $word->{cc}->{pa}->{content});
262             }
263             }
264             elsif (ref $word eq "ARRAY") {
265             # FIXME: include alternatives, if any
266 0           return encode('UTF-8', @{$word}[-1]->{content});
  0            
267             }
268             else {
269 0           return encode('UTF-8', $word);
270             }
271             }
272              
273             sub grapheme_length {
274 0     0 0   my($this, $str) = @_;
275 0           my $count = 0;
276 0           while ($str =~ /\X/g) { $count++ };
  0            
277 0           return $count;
278             }
279              
280             sub maxsize {
281 0     0 1   my($this) = @_;
282 0           return $this->{Maxsize};
283             }
284              
285             sub lines {
286 0     0 1   my($this) = @_;
287 0           return $this->{Linecount};
288             }
289              
290             sub form {
291 0     0 1   my($this) = @_;
292 0           return $this->{Form};
293             }
294              
295             sub debug {
296 0     0 0   my($this, $msg) = @_;
297 0 0         if ($this->{"-Debug"}) {
298 0           print STDERR "%DEBUG: $msg\n";
299             }
300             }
301              
302              
303             1;
304              
305             =encoding ISO8859-1
306              
307             =head1 NAME
308              
309             WWW::Dict::Leo::Org - Interface module to dictionary dict.leo.org
310              
311             =head1 SYNOPSIS
312              
313             use WWW::Dict::Leo::Org;
314             my $leo = new WWW::Dict::Leo::Org();
315             my @matches = $leo->translate($term);
316              
317             =head1 DESCRIPTION
318              
319             B is a module which connects to the website
320             B and translates the given term. It returns an array
321             of hashes. Each hash contains a left side and a right side of the
322             result entry.
323              
324             =head1 OPTIONS
325              
326             B has several parameters, which can be supplied as a hash.
327              
328             All parameters are optional.
329              
330             =over
331              
332             =item I<-Host>
333              
334             The hostname of the dict website to use. For the moment only dict.leo.org
335             is supported, which is also the default - therefore changing the
336             hostname would not make much sense.
337              
338             =item I<-Port>
339              
340             The tcp port to use for connecting, the default is 80, you shouldn't
341             change it.
342              
343             =item I<-UserAgent>
344              
345             The user-agent to send to dict.leo.org site. Currently this is the
346             default:
347              
348             Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9
349              
350             =item I<-Proxy>
351              
352             Fully qualified proxy server. Specify as you would do in the well
353             known environment variable B, example:
354              
355             -Proxy => "http://192.168.1.1:3128"
356              
357             =item I<-ProxyUser> I<-ProxyPass>
358              
359             If your proxy requires authentication, use these parameters
360             to specify the credentials.
361              
362             =item I<-Debug>
363              
364             If enabled (set to 1), prints a lot of debug information to
365             stderr, normally only required for developers or to
366             report bugs (see below).
367              
368             =back
369              
370             Parameters to control behavior of dict.leo.org:
371              
372             =over
373              
374             =item I<-Language>
375              
376             Translation direction. Please note that dict.leo.org always translates
377             either to or from german.
378              
379             The following languages are supported: english, polish, spanish, portuguese
380             russian and chinese.
381              
382             You can specify only the country code, or append B in order to
383             force translation to german, or preprend B in order to translate
384             to the other language.
385              
386             Valid examples:
387              
388             ru to or from russian
389             de2pl to polish
390             es2de spanish to german
391              
392             Valid country codes:
393              
394             en english
395             es spanish
396             ru russian
397             pt portuguese
398             pl polish
399             ch chinese
400              
401             Default: B.
402              
403             =back
404              
405             =head1 METHODS
406              
407             =head2 translate($term)
408              
409             Use this method after initialization to connect to dict.leo.org
410             and translate the given term. It returns an array of hashes containing
411             the actual results.
412              
413             use WWW::Dict::Leo::Org;
414             use Data::Dumper;
415             my $leo = new WWW::Dict::Leo::Org();
416             my @matches = $leo->translate("test");
417             print Dumper(\@matches);
418              
419             which prints:
420              
421             $VAR1 = [
422             {
423             'data' => [
424             {
425             'left' => 'check',
426             'right' => 'der Test'
427             },
428             {
429             'left' => 'quiz (Amer.)',
430             'right' => 'der Test    [Schule]'
431             ],
432             'title' => 'Unmittelbare Treffer'
433             },
434             {
435             'data' => [
436             {
437             'left' => 'to fail a test',
438             'right' => 'einen Test nicht bestehen'
439             },
440             {
441             'left' => 'to test',
442             'right' => 'Tests macheneinen Test machen'
443             }
444             ],
445             'title' => 'Verben und Verbzusammensetzungen'
446             },
447             'data' => [
448             {
449             'left' => 'testing  adj.',
450             'right' => 'im Test'
451             }
452             ],
453             'title' => 'Wendungen und Ausdrücke'
454             }
455             ];
456              
457              
458             You might take a look at the B script how to process
459             this data.
460              
461             =head2 maxsize()
462              
463             Returns the size of the largest returned term (left side).
464              
465             =head2 lines()
466              
467             Returns the number of translation results.
468              
469             =head2 form()
470              
471             Returns the submitted form uri.
472              
473             =head1 SEE ALSO
474              
475             L
476              
477             =head1 COPYRIGHT
478              
479             WWW::Dict::Leo::Org - Copyright (c) 2007-2017 by Thomas v.D.
480              
481             L -
482             Copyright (c) 1995-2016 LEO Dictionary Team.
483              
484             =head1 AUTHOR
485              
486             Thomas v.D.
487              
488             =head1 HOW TO REPORT BUGS
489              
490             Use L to report bugs, select the queue for B.
491              
492             Please don't forget to add debugging output!
493              
494             =head1 VERSION
495              
496             2.00
497              
498             =cut