source: trunk/gsdl/perllib/unicode.pm@ 3146

Last change on this file since 3146 was 2713, checked in by sjboddie, 23 years ago

* empty log message *

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