source: main/tags/2.36/gsdl/perllib/unicode.pm@ 27774

Last change on this file since 27774 was 2483, checked in by say1, 23 years ago

added a "if" to catch the case where someone tries to convert an undefined string

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 11.8 KB
Line 
1###########################################################################
2#
3# unicode.pm --
4# A component of the Greenstone digital library software
5# from the New Zealand Digital Library Project at the
6# University of Waikato, New Zealand.
7#
8# Copyright (C) 1999 New Zealand Digital Library Project
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23#
24###########################################################################
25
26# useful functions for dealing with Unicode
27
28# Unicode strings are stored as arrays of scalars as perl
29# lacks characters are 8-bit (currently)
30
31package unicode;
32use encodings;
33
34# ascii2unicode takes an (extended) ascii string (ISO-8859-1)
35# and returns a unicode array.
36sub ascii2unicode {
37 my ($in) = @_;
38 my $out = [];
39
40 my $i = 0;
41 my $len = length($in);
42 while ($i < $len) {
43 push (@$out, ord(substr ($in, $i, 1)));
44 $i++;
45 }
46
47 return $out;
48}
49
50# ascii2utf8 takes a reference to an (extended) ascii string and returns a
51# UTF-8 encoded string. This is just a faster version of
52# "&unicode2utf8(&ascii2unicode($str));"
53sub ascii2utf8 {
54 my ($in) = @_;
55 my $out = "";
56
57 if (!defined($in)|| !defined($$in)) {
58 return $out;
59 }
60
61 my ($c);
62 my $i = 0;
63 my $len = length($$in);
64 while ($i < $len) {
65 $c = ord (substr ($$in, $i, 1));
66 if ($c < 0x80) {
67 # ascii character
68 $out .= chr ($c);
69
70 } else {
71 # extended ascii character
72 $out .= chr (0xc0 + (($c >> 6) & 0x1f));
73 $out .= chr (0x80 + ($c & 0x3f));
74 }
75 $i++;
76 }
77
78 return $out;
79}
80
81# unicode2utf8 takes a unicode array as input and encodes it
82# using utf-8
83sub unicode2utf8 {
84 my ($in) = @_;
85 my $out = "";
86
87 foreach $num (@$in) {
88 next unless defined $num;
89 if ($num < 0x80) {
90 $out .= chr ($num);
91
92 } elsif ($num < 0x800) {
93 $out .= chr (0xc0 + (($num >> 6) & 0x1f));
94 $out .= chr (0x80 + ($num & 0x3f));
95
96 } elsif ($num < 0xFFFF) {
97 $out .= chr (0xe0 + (($num >> 12) & 0xf));
98 $out .= chr (0x80 + (($num >> 6) & 0x3f));
99 $out .= chr (0x80 + ($num & 0x3f));
100
101 } else {
102 # error, don't encode anything
103 die;
104 }
105 }
106 return $out;
107}
108
109# utf82unicode takes a utf-8 string and produces a unicode
110# array
111sub utf82unicode {
112 my ($in) = @_;
113 my $out = [];
114
115 my $i = 0;
116 my ($c1, $c2, $c3);
117 $len = length($in);
118 while ($i < $len) {
119 if (($c1 = ord(substr ($in, $i, 1))) < 0x80) {
120 # normal ascii character
121 push (@$out, $c1);
122
123 } elsif ($c1 < 0xc0) {
124 # error, was expecting the first byte of an
125 # encoded character. Do nothing.
126
127 } elsif ($c1 < 0xe0 && $i+1 < $len) {
128 # an encoded character with two bytes
129 $c2 = ord (substr ($in, $i+1, 1));
130 if ($c2 >= 0x80 && $c2 < 0xc0) {
131 # everything looks ok
132 push (@$out, ((($c1 & 0x1f) << 6) +
133 ($c2 & 0x3f)));
134 $i++; # gobbled an extra byte
135 }
136
137 } elsif ($c1 < 0xf0 && $i+2 < $len) {
138 # an encoded character with three bytes
139 $c2 = ord (substr ($in, $i+1, 1));
140 $c3 = ord (substr ($in, $i+2, 1));
141 if ($c2 >= 0x80 && $c2 < 0xc0 &&
142 $c3 >= 0x80 && $c3 < 0xc0) {
143 # everything looks ok
144 push (@$out, ((($c1 & 0xf) << 12) +
145 (($c2 & 0x3f) << 6) +
146 ($c3 & 0x3f)));
147
148 $i += 2; # gobbled an extra two bytes
149 }
150
151 } else {
152 # error, only decode Unicode characters not full UCS.
153 # Do nothing.
154 }
155
156 $i++;
157 }
158
159 return $out;
160}
161
162# unicode2ucs2 takes a unicode array and produces a UCS-2
163# unicode string (every two bytes forms a unicode character)
164sub unicode2ucs2 {
165 my ($in) = @_;
166 my $out = "";
167
168 foreach $num (@$in) {
169 $out .= chr (($num & 0xff00) >> 8);
170 $out .= chr ($num & 0xff);
171 }
172
173 return $out;
174}
175
176# ucs22unicode takes a UCS-2 string and produces a unicode array
177sub ucs22unicode {
178 my ($in) = @_;
179 my $out = [];
180
181 my $i = 0;
182 my $len = length ($in);
183 while ($i+1 < $len) {
184 push (@$out, ord (substr($in, $i, 1)) << 8 +
185 ord (substr($in, $i+1, 1)));
186
187 $i ++;
188 }
189
190 return $out;
191}
192
193# takes a reference to a string and returns a reference to a unicode array
194sub convert2unicode {
195 my ($encoding, $textref) = @_;
196
197 if (!defined $encodings::encodings->{$encoding}) {
198 print STDERR "unicode::convert2unicode: ERROR: Unsupported encoding ($encoding)\n";
199 return [];
200 }
201
202 my $encodename = "$encoding-unicode";
203 my $enc_info = $encodings::encodings->{$encoding};
204 my $mapfile = &util::filename_cat($ENV{'GSDLHOME'}, "mappings",
205 "to_uc", $enc_info->{'mapfile'});
206 if (!&loadmapencoding ($encodename, $mapfile)) {
207 print STDERR "unicode: ERROR - could not load encoding $encodename\n";
208 return [];
209 }
210
211 if (defined $enc_info->{'converter'}) {
212 my $converter = $enc_info->{'converter'};
213 return &$converter ($encodename, $textref);
214 }
215
216 if ($translations{$encodename}->{'count'} == 1) {
217 return &singlebyte2unicode ($encodename, $textref);
218 } else {
219 return &doublebyte2unicode ($encodename, $textref);
220 }
221}
222
223# singlebyte2unicode converts simple 8 bit encodings where characters below
224# 0x80 are normal ascii characters and the rest are decoded using the
225# appropriate mapping files.
226#
227# Examples of encodings that may be converted using singlebyte2unicode are
228# the iso-8859 and windows-125* series).
229sub singlebyte2unicode {
230 my ($encodename, $textref) = @_;
231
232 my @outtext = ();
233 my $len = length($$textref);
234 my ($c);
235 my $i = 0;
236
237 while ($i < $len) {
238 if (($c = ord(substr($$textref, $i, 1))) < 0x80) {
239 # normal ascii character
240 push (@outtext, $c);
241 } else {
242 $c = &transchar ($encodename, $c);
243 # put a black square if cannot translate
244 $c = 0x25A1 if $c == 0;
245 push (@outtext, $c);
246 }
247 $i ++;
248 }
249 return \@outtext;
250}
251
252# doublebyte2unicode converts simple two byte encodings where characters
253# below code point 0x80 are single-byte characters and the rest are
254# double-byte characters.
255#
256# Examples of encodings that may be converted using doublebyte2unicode are
257# CJK encodings like GB encoded Chinese and UHC Korean.
258#
259# Note that no error checking is performed to make sure that the input text
260# is valid for the given encoding.
261#
262# Also, encodings that may contain characters of more than two bytes are
263# not supported (any EUC encoded text may in theory contain 3-byte
264# characters but in practice only one and two byte characters are used).
265sub doublebyte2unicode {
266 my ($encodename, $textref) = @_;
267
268 my @outtext = ();
269 my $len = length($$textref);
270 my ($c1, $c2);
271 my $i = 0;
272
273 while ($i < $len) {
274 if (($c1 = ord(substr($$textref, $i, 1))) >= 0x80) {
275 if ($i+1 < $len) {
276 # double-byte character
277 $c2 = ord(substr($$textref, $i+1, 1));
278 my $c = &transchar ($encodename, ($c1 << 8) | $c2);
279 # put a black square if cannot translate
280 $c = 0x25A1 if $c == 0;
281 push (@outtext, $c);
282 $i += 2;
283
284 } else {
285 # error
286 print STDERR "unicode: ERROR missing second half of double-byte character\n";
287 $i++;
288 }
289
290 } else {
291 # single-byte character
292 push (@outtext, $c1);
293 $i++;
294 }
295 }
296 return \@outtext;
297}
298
299# Shift-JIS to unicode
300# We can't use doublebyte2unicode for Shift-JIS because it uses some
301# single-byte characters above code point 0x80 (i.e. half-width katakana
302# characters in the range 0xA1-0xDF)
303sub shiftjis2unicode {
304 my ($encodename, $textref) = @_;
305
306 my @outtext = ();
307 my $len = length($$textref);
308 my ($c1, $c2);
309 my $i = 0;
310
311 while ($i < $len) {
312 $c1 = ord(substr($$textref, $i, 1));
313
314 if (($c1 >= 0xA1 && $c1 <= 0xDF) || $c1 == 0x5c || $c1 == 0x7E) {
315 # Single-byte half-width katakana character or
316 # JIS Roman yen or overline characters
317 my $c = &transchar ($encodename, $c1);
318 # - put a black square if cannot translate
319 $c = 0x25A1 if $c == 0;
320 push (@outtext, $c);
321 $i++;
322
323 } elsif ($c1 < 0x80) {
324 # ASCII
325 push (@outtext, $c1);
326 $i ++;
327
328 } elsif ($c1 < 0xEF) {
329 if ($i+1 < $len) {
330 $c2 = ord(substr($$textref, $i+1, 1));
331 if (($c2 >= 0x40 && $c2 <= 0x7E) || ($c2 >= 0x80 && $c2 <= 0xFC)) {
332 # Double-byte shift-jis character
333 my $c = &transchar ($encodename, ($c1 << 8) | $c2);
334 # put a black square if cannot translate
335 $c = 0x25A1 if $c == 0;
336 push (@outtext, $c);
337 } else {
338 # error
339 print STDERR "unicode: ERROR Invalid Shift-JIS character\n";
340 }
341 $i += 2;
342 } else {
343 # error
344 print STDERR "unicode: ERROR missing second half of Shift-JIS character\n";
345 $i ++;
346 }
347 } else {
348 # error
349 print STDERR "unicode: ERROR Invalid Shift-JIS character\n";
350 $i ++;
351 }
352 }
353 return \@outtext;
354}
355
356sub transchar {
357 my ($encoding, $from) = @_;
358 my $high = ($from / 256) % 256;
359 my $low = $from % 256;
360
361 return 0 unless defined $translations{$encoding};
362
363 my $block = $translations{$encoding}->{'map'};
364
365 if (ref ($block->[$high]) ne "ARRAY") {
366 return 0;
367 }
368 return $block->[$high]->[$low];
369}
370
371# %translations is of the form:
372#
373# encodings{encodingname-encodingname}->{'map'}->blocktranslation
374# blocktranslation->[[0-255],[256-511], ..., [65280-65535]]
375#
376# Any of the top translation blocks can point to an undefined
377# value. This data structure aims to allow fast translation and
378# efficient storage.
379%translations = ();
380
381# @array256 is used for initialisation, there must be
382# a better way...
383@array256 = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
384 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
385 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
386 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
387 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
388 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
389 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
390 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
391 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
392 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
393 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
394 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
395 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
396 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
397 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
398 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
399
400# returns 1 if successful, 0 if unsuccessful
401sub loadmapencoding {
402 my ($encoding, $mapfile) = @_;
403
404 # check to see if the encoding has already been loaded
405 return 1 if (defined $translations{$encoding});
406
407 return 0 unless open (MAPFILE, $mapfile);
408 binmode (MAPFILE);
409
410 $translations{$encoding} = {'map' => [@array256], 'count' => 0};
411 my $block = $translations{$encoding};
412
413 my ($in,$i,$j);
414 while (read(MAPFILE, $in, 1) == 1) {
415 $i = unpack ("C", $in);
416 $block->{'map'}->[$i] = [@array256];
417 for ($j=0; $j<256 && read(MAPFILE, $in, 2)==2; $j++) {
418 my ($n1, $n2) = unpack ("CC", $in);
419 $block->{'map'}->[$i]->[$j] = ($n1*256) + $n2;
420 }
421 $block->{'count'} ++;
422 }
423
424 close (MAPFILE);
425}
426
427sub unicode2koi8r {
428 my ($uniref) = @_;
429
430 my $outtext = "";
431 my $encodename = "unicode-koi8_r";
432 my $enc_info = $encodings::encodings->{"koi8_r"};
433 my $mapfile = &util::filename_cat($ENV{'GSDLHOME'}, "mappings",
434 "from_uc", $enc_info->{'mapfile'});
435 if (!&loadmapencoding ($encodename, $mapfile)) {
436 print STDERR "unicode: ERROR - could not load encoding $encodename\n";
437 return "";
438 }
439
440 foreach my $c (@$uniref) {
441 if ($c < 0x80) {
442 # normal ascii character
443 $outtext .= chr($c);
444 } else {
445 # extended ascii character
446 $c = &transchar ($encodename, $c);
447
448 # put a question mark if cannot translate
449 if ($c == 0) {
450 $outtext .= "?";
451 } else {
452 $outtext .= chr($c);
453 }
454 }
455 }
456 return $outtext;
457}
458
4591;
Note: See TracBrowser for help on using the repository browser.