File Coverage

blib/lib/Lingua/Translate/SysTran.pm
Criterion Covered Total %
statement 77 91 84.6
branch 24 42 57.1
condition 2 9 22.2
subroutine 12 14 85.7
pod 4 4 100.0
total 119 160 74.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             # Copyright (c) 2002, Sam Vilain. All rights reserved. This program
4             # is free software; you may use it under the same terms as Perl
5             # itself.
6              
7             package Lingua::Translate::SysTran;
8              
9 1     1   5 use strict;
  1         1  
  1         35  
10 1     1   5 use Carp;
  1         2  
  1         83  
11              
12             # package globals:
13             # %config is default values to use for new objects
14             # %servers is a hash from a translation pair to a hostname/port number
15             # %one_letter_codes is actually a constant, it is for default port
16             # number calculation
17 1     1   6 use vars qw($VERSION %config %servers %one_letter_codes);
  1         2  
  1         91  
18              
19             # WARNING: Some constants have their default values extracted from the
20             # POD. See the Pod::Constants man page.
21              
22             =head1 NAME
23              
24             Lingua::Translate::SysTrans - Translation back-end for SysTran's
25             enterprise translation server, version
26             0.01 (European languages only)
27              
28             =head1 SYNOPSIS
29              
30             use Lingua::Translate;
31              
32             Lingua::Translate::config
33             (
34             back_end => "SysTran",
35             host => "babelfish.mydomainname.com",
36             );
37              
38             my $xl8r = Lingua::Translate->new(src => "de", dest => "en");
39              
40             # prints "My hovercraft is full of eels"
41             print $xl8r->translate("Mein Luftkissenfahrzeug ist voll von den Aalen");
42              
43             =head1 DESCRIPTION
44              
45             Lingua::Translate::SysTran is a translation back-end for
46             Lingua::Translate that contacts a SysTran translation server to do the
47             real work.
48              
49             You should try to avoid putting the config() command that sets the
50             location of the server in all of your scripts; make a little
51             configuration module or put it in a script you can `require'.
52              
53             =head1 CONSTRUCTOR
54              
55             =head2 new(src => $lang, dest => lang, option => $value)
56              
57             Creates a new translation handle. This won't initiate a connection
58             until you try to translate something.
59              
60             =over
61              
62             =item src
63              
64             Source language, in RFC-3066 form. See L for a
65             discussion of RFC-3066 language tags.
66              
67             =item dest
68              
69             Destination Language
70              
71             =item host
72              
73             Specify the host to contact
74              
75             =item port
76              
77             Specify the port number
78              
79             =back
80              
81             =cut
82              
83 1     1   6 use I18N::LangTags qw(is_language_tag);
  1         2  
  1         252  
84              
85             sub new {
86 1     1 1 9 my ($class, %options) = (@_);
87              
88 1         7 my $self = bless { %config }, $class;
89              
90 1 50 33     9 croak "Must supply source and destination language"
91             unless (defined $options{src} and defined $options{dest});
92              
93 1 50       9 is_language_tag($self->{src} = delete $options{src})
94             or croak "$self->{src} is not a valid RFC3066 language tag";
95              
96 1 50       19 is_language_tag($self->{dest} = delete $options{dest})
97             or croak "$self->{dest} is not a valid RFC3066 language tag";
98              
99 1         14 $self->config(%options);
100              
101 1         4 $self->{pair} = $self->{src} . "_" . $self->{dest};
102              
103 1         3 my $custom_port = $servers{$self->{pair}};
104              
105 1 50       4 if ( defined $custom_port ) {
106 0         0 ($self->{host}, $self->{port})
107             = ($custom_port =~ m/^(.*)(?: (:\d+) )$/);
108             }
109              
110 1   33     9 $self->{port} ||= _default_port($self->{pair});
111              
112 1         6 return $self;
113             }
114              
115             =head1 METHODS
116              
117             The following methods may be called on Lingua::Translate::SysTran
118             objects.
119              
120             =head2 translate($text) : $translated
121              
122             Translates the given text. die's on any kind of error.
123              
124             =cut
125              
126 1     1   5 use IO::Socket;
  1         1  
  1         29  
127             BEGIN {
128             # use Unicode::MapUTF8 if it is available
129 1     1   1290 eval "use Unicode::MapUTF8 qw(from_utf8 to_utf8);";
  1     1   7  
  1         1  
  1         49  
130 1 50       1984 if ( $@ ) {
131 0         0 eval 'no strict; sub from_utf8 { %a=(@_); $a{"-string"} } '.
132             '*{to_utf8} = \&from_utf8';
133             }
134             };
135              
136             sub translate {
137 1     1 1 3 my $self = shift;
138 1 50       5 UNIVERSAL::isa($self, __PACKAGE__)
139             or croak __PACKAGE__."::translate() called as function";
140              
141             # every back-end we know of speaks ISO-8859-1
142 1         9 my $text = from_utf8( -string => (shift),
143             -charset => "iso-8859-1" );
144              
145 1         243 my $translated;
146              
147 1         6 my $request = (
148             "METHOD=SOCKET\n".
149             "ACTION=TRANSLATE\n".
150             "SOURCE-CONTENT=".length($text)."\n".
151             "$text\n"
152             );
153              
154 1         22 my $socket = IO::Socket::INET->new
155             (
156             Proto => 'tcp',
157             PeerAddr => $self->{host},
158             PeerPort => $self->{port},
159             Reuse => 1,
160             );
161              
162 1 50       1513 $self->_barf("Connection failed; $!") unless $socket;
163              
164             ## Sending request
165 1 50       23 $socket->write($request, length($request))
166             || $self->_barf ('write failed; '.$!);
167              
168 1         87 $socket->flush;
169              
170             ## Then waiting for answer
171 1         2 my ($error, $error_message, $time);
172 1         64 while ($_ = $socket->getline()) {
173 3 50       1173 my ($command, $value) = (m/^([\w\-]+)=(.*)$/)
174             or $self->_barf("protocol error");
175              
176 3 100       21 if ( $command eq "ERR" ) {
    100          
    50          
    50          
177 1         34 $error = $value;
178             } elsif ( $command eq "TIME" ) {
179 1         208 $time = $value;
180             } elsif ( $command eq "EMSG" ) {
181 0         0 $error_message = $value;
182             } elsif ( $command eq "OUTPUT-CONTENT" ) {
183             # data always follows
184 1         12 my $bytes_read = $socket->read($translated, $value);
185 1 50       18 ($bytes_read == $value)
186             or $self->_barf("short read");
187 1         3 last;
188             } else {
189 0         0 $self->_barf("protocol mismatch; $command");
190             }
191             }
192              
193             # close connection
194 1         9 $socket->close;
195              
196 1 50       68 $self->_barf($error_message) if $error;
197              
198             # trim excess line feeds at end of string
199 1         6 $translated =~ s/\n*$//;
200              
201 1         11 return to_utf8( -string => $translated,
202             -charset => "iso-8859-1" );
203             }
204              
205             sub _barf {
206 0     0   0 my $self = shift;
207 0         0 my $message = shift;
208              
209 0         0 die ($message . " talking to $self->{host}:$self->{port} "
210             .$self->{pair} );
211              
212             }
213              
214             =head2 available() : @list
215              
216             Returns a list of available language pairs, in the form of "XX_YY",
217             where XX is the source language and YY is the destination. If you
218             want the english name of a language tag, call
219             I18N::LangTags::List::name() on it. See L.
220              
221             If you call this function without configuring the package, it returns
222             all of the languages that there are known back-ends for.
223              
224             =cut
225              
226             sub available {
227              
228 0     0 1 0 my $self = shift;
229 0 0       0 UNIVERSAL::isa($self, __PACKAGE__)
230             or croak __PACKAGE__."::available() called as function";
231              
232 0         0 my @a = keys %one_letter_codes;
233              
234             # English; "the new universal language?"
235             # mi spitu fo le bango pe le glico
236             return (
237             keys %servers ||
238 0   0     0 grep /en/, ( map { my $a=$_; map{"${_}_$a"} my @a } @a )
239             );
240              
241             }
242              
243             =head1 CONFIGURATION FUNCTIONS
244              
245             =head2 config(option => $value)
246              
247             This function sets defaults for use when constructing objects.
248              
249             =cut
250              
251             sub config {
252              
253 2     2 1 4 my $self;
254 2 100       25 if ( UNIVERSAL::isa($_[0], __PACKAGE__) ) {
255 1         2 $self = shift;
256             } else {
257 1         2 $self = \%config;
258             }
259              
260 2         11 while ( my ($option, $value) = splice @_, 0, 2 ) {
261              
262 1 50       7 if ( $option eq "pairs" ) {
    50          
263              
264             # configure a pair
265 0         0 while ( my ($pair, $server) = each %$value ) {
266 0         0 $servers{$pair} = $server;
267             }
268              
269             } elsif ( $option =~ m/^(host|port)$/) {
270              
271             # configure host/port
272 1         7 $self->{$option} = $value;
273              
274             } else {
275              
276 0         0 croak "Unknown configuration option $option";
277             }
278             }
279             }
280              
281             =over
282              
283             =item host
284              
285             Defines the hostname to use if no hostname/port is defined for a
286             language pair. The default value is "localhost". Do not specify a
287             port number.
288              
289             =item servers
290              
291             The value to this configuration option must be a hash reference from a
292             language pair (in XX_YY form) to a hostname, optionally followed by a
293             colon and a port number.
294              
295             If this configuration option is defined, then attempts to translate
296             undefined languages will fail. There is no default value for this
297             option.
298              
299             =back
300              
301             =head1 A Note on default port numbers
302              
303             Returns the host name and port number for the given language pair.
304              
305             To determine the default port number, take the one-letter code for the
306             language from the below table, express as a number in base 25 (A=0,
307             B=1, etc) and then add 10000 decimal. Eg en => de would be EG, which
308             is 106 decimal, or port 10106.
309              
310             =head2 ONE LETTER LANGUAGE CODES
311              
312             en => E
313             de => G
314             it => I
315             fr => F
316             pt => P
317             es => S
318             el => K
319              
320             =cut
321              
322             sub _default_port {
323 1     1   2 my $pair = shift;
324              
325 1 50       10 my ($src, $tgt) =
326             ($pair =~ m/^(..)_(..)/)
327             or croak "$pair is not a valid language pair";
328              
329             # FIXME - won't work on EBCDIC systems
330 1         4 my $A = ord("A");
331 1         6 my $num = ( (ord($one_letter_codes{$src}) - $A) * 25
332             +ord($one_letter_codes{$tgt}) - $A );
333              
334 1         4 return $num + 10000;
335             }
336              
337             # extract configuration options from the POD
338             use Pod::Constants
339 1         738 'NAME' => sub { ($VERSION) = (m/(\d+\.\d+)/); },
340             'CONFIGURATION FUNCTIONS' => sub {
341             Pod::Constants::add_hook
342             ('*item' => sub {
343 2         676 my ($varname) = m/(\w+)/;
344             #my ($default) = m/The default value is\s+"(.*)"\./s;
345 2         9 my ($default) = m/The default value is\s+"(.*)"/s;
346 2 100       11 config($varname => $default) if $default;
347             }
348 1         2757 );
349             Pod::Constants::add_hook
350             (
351             '*back' => sub {
352              
353             # an ugly hack?
354 1         63 $config{agent} .= $VERSION;
355              
356 1         5 Pod::Constants::delete_hook('*item');
357 1         23 Pod::Constants::delete_hook('*back');
358             }
359 1         29 );
360             },
361 1     1   9 'ONE LETTER LANGUAGE CODES' => \%one_letter_codes;
  1         2  
  1         20  
362              
363             =head1 BUGS/TODO
364              
365             No support for non-ISO-8859-1 character sets - with the software I
366             have, there is no option.
367              
368             =head1 SEE ALSO
369              
370             L, L, L
371              
372             =head1 AUTHOR
373              
374             Sam Vilain,
375              
376             =cut
377              
378             1;