File Coverage

blib/lib/Unicode/Map.pm
Criterion Covered Total %
statement 210 555 37.8
branch 80 246 32.5
condition 14 57 24.5
subroutine 27 59 45.7
pod 15 20 75.0
total 346 937 36.9


line stmt bran cond sub pod time code
1             #
2             # $Id$
3             #
4             # Unicode::Map 0.112
5             #
6             # Documentation at end of file.
7             #
8             # Copyright (C) 1998, 1999, 2000 Martin Schwartz. All rights reserved.
9             # This program is free software; you can redistribute it and/or
10             # modify it under the same terms as Perl itself.
11             #
12             # Contact: Martin Schwartz
13             #
14              
15             package Unicode::Map;
16 3     3   2795 use strict;
  3         6  
  3         113  
17 3     3   14 use vars qw($VERSION $WARNINGS @ISA $DEBUG);
  3         5  
  3         235  
18 3     3   14 use Carp;
  3         12  
  3         20355  
19              
20             $VERSION='0.112'; # Michael Changes it to 0.112
21              
22             require DynaLoader; @ISA=qw(DynaLoader);
23             bootstrap Unicode::Map $VERSION;
24              
25             sub NOISE () { 1 }
26              
27             sub MAGIC () { 0xB827 } # magic word
28              
29             sub M_END () { 0 } # end
30            
31             sub M_INF () { 1 } # infinite subsequent entries (default)
32             sub M_BYTE () { 2 } # 1..255 subsequent entries
33            
34             sub M_VER () { 4 } # (Internal) file format revision.
35            
36             sub M_AKV () { 6 } # key1, val1, key2, val2, ... (default)
37             sub M_AKAV () { 7 } # key1, key2, ..., val1, val2, ...
38             sub M_PKV () { 8 } # partial key value mappings
39            
40             sub M_CKn () { 10 } # compress keys not
41             sub M_CK () { 11 } # compress keys (default)
42            
43             sub M_CVn () { 13 } # compress values not
44             sub M_CV () { 14 } # compress values (default)
45              
46             ##
47             ## The next entries are for info, only. They are stored as unicode strings.
48             ##
49              
50             sub I_NAME () { 20 } # Character Set Name
51             sub I_ALIAS () { 21 } # Character Set alias name (several entries allowed)
52             sub I_VER () { 22 } # Mapfile revision
53             sub I_AUTH () { 23 } # Mapfile authRess
54             sub I_INFO () { 24 } # Some userEss definable string
55              
56             sub WARN_DEFAULT () { 0x0000 };
57             sub WARN_DEPRECATION () { 0x1000 };
58             sub WARN_COMPATIBILITY () { 0x2000 };
59              
60             ##
61             ## --- Init ---------------------------------------------------------------
62             ##
63              
64             my $MAP_Pathname = 'Unicode/Map';
65             my $MAP_Path = $INC{"Unicode/Map.pm"}; $MAP_Path=~s/\.pm//;
66             die "Can't find base directory of Unicode::Map!" unless $MAP_Path;
67              
68             my @order = (
69             { 1=>"C", 2=>"n", 3=>"N", 4=>"N" }, # standard ("Network order")
70             { 1=>"C", 2=>"v", 3=>"V", 4=>"V" }, # reverse ("Vax order")
71             );
72              
73             my %registry = ();
74             my %mappings = ();
75             my $registry_loaded = 0;
76              
77             $WARNINGS = WARN_DEFAULT;
78             _init_registry();
79              
80             ##
81             ## --- public conversion methods ------------------------------------------
82             ##
83              
84             # For compatibility with Unicode::Map8
85 0     0 1 0 sub to8 { goto &from_unicode }
86              
87             sub from_unicode {
88 10     10 1 45 my $S = shift;
89 10 100       25 if ( $#_==0 ) {
90 9         16 $S -> _to ("TO_CUS", $S->_csid(), @_);
91             } else {
92 1         3 _deprecated ( );
93 1         3 _incompatible ( );
94 1         3 $S -> _to ("TO_CUS", @_);
95             }
96             }
97              
98             sub new {
99             #
100             # $ref||undef = Unicode::Map->new("ISO-8859-1")
101             #
102             # Note: usage like below is deprecated. It is not compatible with
103             # Unicode::Map8. Support will vanish soon! martin [2000-Jun-19]
104             #
105             # I<$Map> = new Unicode::Map;
106             #
107             # I<$utf16> = I<$Map> -> to_unicode ("ISO-8859-1", "Hello world!");
108             # => $_16bit == "\0H\0e\0l\0l\0o\0 \0w\0o\0r\0l\0d\0!"
109             #
110             # I<$locale> = I<$Map> -> from_unicode ("ISO-8859-7", I<$_16bit>);
111             # => $_8bit == "Hello world!"
112 9     9 1 1827 my ($proto, $parH) = @_;
113 9   33     66 my $S = bless ({}, ref($proto) || $proto);
114 9         30 $S -> _noise ( NOISE );
115 9 50       32 return unless $S -> _load_registry ( );
116 9 100       32 if (!$parH) {
117 1         4 _deprecated ( );
118             } else {
119 8         11 my $csid;
120 8 100       22 if (!ref($parH)) {
121             # Compatible to Unicode::Map8
122 7         11 $csid = $parH;
123             } else {
124 1         3 _deprecated ( );
125 1         3 _incompatible ( );
126 1 50       4 if ( $parH->{"STARTUP"} ) {
127 0         0 $S -> Startup ( $parH->{"STARTUP"} );
128             }
129 1         2 $csid = $parH -> { "ID" };
130             }
131 8 50       20 if ( $csid ) {
132 8 50       23 return 0 unless $S -> _csid ( $S->_real_id($csid) )
133             }
134             }
135 9         27 $S;
136             }
137              
138             # Deprecated!
139             sub noise {
140 1     1 1 8 _deprecated ( );
141 1         3 _incompatible ( );
142             # Defines the verbosity of messages to user sent via I<$Startup>. Can be no
143             # messages at all (n=0), some information (n=1) or some more information
144             # (n=3). Default is n=1.
145             # I<$Map> -> noise (I<$n>)
146 1         2 _noise ( @_ );
147             }
148 42     42   127 sub _noise { shift->_member("P_NOISE", @_) }
149              
150             #
151             # Unicode::Map.xs -> reverse_unicode
152             #
153             # Usage is deprecated! Use Unicode::String::byteswap instead!
154             #
155             # I<$string> = I<$Map> -> reverse_unicode (I<$string>)
156             #
157             # One Unicode character, precise one utf16 character, consists of two
158             # bytes. Therefore it is important, in which order these bytes are stored.
159             # As far as I could figure out, Unicode characters are assumed to be in
160             # "Network order" (0x1234 => 0x12, 0x34). Alas, many PC Windows documents
161             # store Unicode characters internally in "Vax order" (0x1234 => 0x34, 0x12).
162             # With this method you can convert "Vax mode" -> "Network mode" and vice versa.
163             #
164             # reverse_unicode changes the original variable if in a void context. If
165             # in scalar or list context returns a new created string.
166             #
167             sub reverse_unicode {
168 2     2 1 15 _deprecated ( "see: Unicode::String::byteswap" );
169 2         4 _incompatible ( );
170 2         8 &_reverse_unicode;
171             }
172              
173             # For compatibility with Unicode::Map8
174 0     0 1 0 sub to16 { goto &to_unicode }
175              
176             sub to_unicode {
177 6     6 1 43 my $S = shift;
178 6 100       18 if ( $#_==0 ) {
179 5         15 $S -> _to ("TO_UNI", $S->_csid(), @_);
180             } else {
181 1         6 _deprecated ( );
182 1         3 _incompatible ( );
183 1         5 $S -> _to ("TO_UNI", @_);
184             }
185             }
186              
187             ##
188             ## --- public maintainance methods ----------------------------------------
189             ##
190              
191             sub alias {
192 0     0 1 0 _incompatible ( );
193 0         0 @{$registry{$_[1]} -> {"ALIAS"}};
  0         0  
194             }
195              
196             sub dest {
197 0     0 0 0 _deprecated ( "'dest' is now 'mapping'" );
198 0         0 goto &mapping;
199             }
200              
201             sub mapping {
202 0     0 1 0 _incompatible ( );
203 0         0 return shift -> _mapping ( shift() );
204             }
205              
206             sub id {
207 0     0 1 0 _incompatible ( );
208 0         0 shift->_real_id(shift());
209             }
210              
211             sub ids {
212 0     0 1 0 _incompatible ( );
213 0         0 (sort {$a cmp $b} grep {!/^GENERIC$/i} keys %registry);
  0         0  
  0         0  
214             }
215              
216             sub info {
217 0     0 0 0 _incompatible ( );
218 0         0 $registry{$_[1]} -> {"INFO"};
219             }
220              
221             sub read_text_mapping {
222 0     0 1 0 _incompatible ( );
223 0         0 my ($S, $csid, $textpath, $style) = @_;
224 0 0       0 return 0 if !($csid = $S->id($csid));
225 0 0       0 $S->_msg("reading") if $S->_noise>0;
226 0         0 $S->_read_text_mapping($csid, $textpath, $style);
227             }
228              
229             sub src {
230 0     0 1 0 _incompatible ( );
231 0         0 $registry{$_[1]} -> {"SRC"};
232             }
233              
234             sub srcURL {
235 0     0 0 0 _incompatible ( );
236 0         0 $registry{$_[1]} -> {"SRCURL"};
237             }
238              
239             sub style {
240 0     0 1 0 _incompatible ( );
241 0         0 $registry{$_[1]} -> {"STYLE"};
242             }
243              
244             sub write_binary_mapping {
245 0     0 1 0 _incompatible ( );
246 0         0 my ($S, $csid, $binpath) = @_;
247 0 0       0 return 0 unless ( $csid = $S->id($csid) );
248 0 0       0 $binpath = $S->_mapping($csid) if !$binpath;
249 0 0       0 return 0 unless $binpath;
250 0 0       0 $S->_msg("writing") if $S->_noise>0;
251 0         0 $S->_write_IMap_to_binary($csid, $binpath);
252             }
253              
254             ##
255             ## --- Application program interface --------------------------------------
256             ##
257              
258             sub Startup {
259 6     6 0 21 _deprecated ( "module Startup shouldn't be used any longer" );
260 6         16 shift->_member("STARTUP", @_);
261             }
262              
263             ##
264             ## --- private methods ----------------------------------------------------
265             ##
266              
267 70 50   70   74 sub _member { my $S=shift; my $n=shift if @_; $S->{$n}=shift if @_; $S->{$n}}
  70 100       165  
  70         244  
  70         299  
268              
269 22     22   52 sub _csid { shift->_member("P_CSID", @_) }
270 0 0   0   0 sub _error { my $S=shift; $S->Startup ? $S->Startup->error(@_) : 0 }
  0         0  
271 6 50   6   10 sub _msg { my $S=shift; $S->Startup ? $S->Startup->msg(@_) : 0 }
  6         16  
272 0 0   0   0 sub _msg_fin { my $S=shift; $S->Startup ? $S->Startup->msg_finish(@_) : 0 }
  0         0  
273 0     0   0 sub _IMap { shift->_member("I", @_) }
274              
275 5     5   19 sub _mapping { $registry{$_[1]} -> {"MAP"} }
276              
277             sub _dump {
278 0     0   0 my $S = shift;
279 0         0 print "Dumping Mapping $S:\n";
280 0 0       0 if ($S->Startup) {
281 0         0 print " - Startup object: ".$S->Startup."\n";
282             } else {
283 0         0 print " - no Startup object\n";
284             }
285 0 0       0 if (%registry) {
286 0         0 print " - Mapping: " . (keys %registry) . " entries defined.\n";
287             } else {
288 0         0 print " - No mappings!\n";
289             }
290 0 0       0 if ($S->_IMap) {
291 0         0 print " - IMap:\n";
292 0         0 my ($k,$v); while(($k,$v)=each %{$S->_IMap}) {
  0         0  
  0         0  
293 0         0 printf " %10s => %s\n", $k, $v;
294             }
295             }
296 0 0       0 if (%mappings) {
297 0         0 print " - Mappings:\n";
298 0         0 my ($k,$v); while(($k,$v)=each %mappings) {
  0         0  
299 0         0 printf " %10s => %s\n", $k, $v;
300             }
301             }
302 0         0 1}
303              
304             sub _real_id {
305 24     24   31 my ($S, $csid) = @_;
306 24 50       108 if (!%registry) {
307 0         0 return $S->_error("No mapping definitions!\n");
308             }
309 24 50       122 return $csid if defined $registry{$csid};
310 0         0 my $id="";
311 0         0 my (@tmp, $k, $v);
312 0         0 while (($k,$v) = each %registry) {
313 0 0 0     0 next if !$k || !$v;
314 0 0       0 if ($csid =~ /^$k$/i) {
315 0         0 $id=$k; last;
  0         0  
316             } else {
317 0         0 for (@{$v->{"ALIAS"}}) {
  0         0  
318 0 0       0 if (/^$csid$/i) {
319 0         0 $id=$k; last;
  0         0  
320             }
321             }
322             }
323             }
324 0         0 while (($k, $v) = each %registry) {}
325 0 0       0 return $S->_error("Character Set $csid not defined!") if !$id;
326 0         0 $id;
327             }
328              
329             sub _to {
330             #
331             # 1||0 = $S -> _to ("TO_UNI"||"TO_CUS", $csid, $src||$srcR, $destR, $o, $l)
332             # $text||"" = $S -> _to ("TO_UNI"||"TO_CUS", $csid, $src||$srcR, "", $o, $l)
333             #
334 16     16   29 my ($S, $to, $csid, $srcR, $destR, $o, $l) = @_;
335 16 50       37 return 0 if !($csid = $S->_real_id($csid));
336 16 50       35 return 0 if !$S->_load_TMap($csid);
337              
338 16         30 my ($cs1, $n1, $cs2, $n2, $tmp) = (0, 0, 0, 0, "");
339 16         16 my (@M, @C);
340              
341 16         18 my $destbuf = "";
342 16 50       30 my $srcbuf = ref($srcR) ? $$srcR : $srcR;
343              
344 16         30 my $C = $mappings{$csid}->{$to};
345              
346 16 50       35 if ($S->_noise>2) {
347 0 0       0 $S->_msg("mapping ".(($to=~/^to_unicode$/i) ? "to Unicode" : "to $csid"));
348             }
349 16         18 my ($csa,$na,$csb,$nb);
350 23         94 my @n = sort {
351             # Sort the partial mappings according to their left side's total
352             # length, descending order.
353 16         72 ($csa, $na) = split/,/,$a;
354 23         44 ($csb, $nb) = split/,/,$b;
355 23         63 $csb*$nb <=> $csa*$na
356             } keys %$C;
357 16 100       34 if ($#n==0) {
358 4         49 ($cs1, $n1, $cs2, $n2) = split /,/,$n[0];
359 4   50     45 $destbuf = $S->_map_hash($srcbuf,
      50        
360             $C->{$n[0]},
361             $n1*$cs1,
362             $o||undef, $l||undef
363             );
364             } else {
365 30         70 $destbuf = $S->_map_hashlist($srcbuf,
366             [map $C->{$_}, @n],
367 12         50 [map {($cs1,$n1)=split/,/; int($cs1*$n1)} @n],
  30         223  
368             $o, $l
369             );
370             }
371 16 50       44 if ($destR) {
372 0         0 $$destR=$destbuf; 1;
  0         0  
373             } else {
374 16         76 $destbuf;
375             }
376             }
377              
378             sub _init_registry {
379 3     3   7 %registry = ();
380 3         27 $registry_loaded = 0;
381 3         11 _add_registry_entry("GENERIC", "GENERIC", "GENERIC");
382 3         6 1}
383              
384             sub _unload_registry {
385 0     0   0 _init_registry;
386             }
387              
388             ##
389             ## --- Binary to TMap -----------------------------------------------------
390             ##
391              
392             # TMap structure:
393             #
394             # %T = (
395             # $CSID => {
396             # TO_CUS => {
397             # "$cs_a1,$n_a1,$cs_a2,$n_a2" => {
398             # "str_a1_1" => "str_a2_1", ... ,
399             # "str_a1_n" => "str_a2_n",
400             # }, ... ,
401             # "$cs_x1,$n_x1,$cs_x2,$n_x2" => {
402             # "str_x1_1" => "str_x2_1", ... ,
403             # "str_x1_n" => "str_x2_n",
404             # }
405             # }
406             # TO_UNI => {
407             # "$cs_a2,$n_a2,$cs_a1,$n_a1" => {
408             # "str_a2_1" => "str_a1_1", ... ,
409             # "str_a2_n" => "str_a1_n",
410             # }, ... ,
411             # "$csx2,$nx2,$csx1,$nx1" => {
412             # "str_x2_1" => "str_x1_1", ... ,
413             # "str_x2_n" => "str_x1_n",
414             # }
415             # }
416             # }
417             # );
418              
419             sub _load_TMap {
420 16     16   21 my ($S, $csid) = @_;
421 16 100       50 return 1 if $mappings{$csid};
422 5 50       15 return 0 if !$S->_read_binary_to_TMap($csid);
423 5         28 1}
424              
425             sub _read_binary_to_TMap {
426 5     5   7 my ($S, $csid) = @_;
427 5         13 my %U = ();
428 5         7 my %C = ();
429 5         7 my $buf = "";
430              
431             #
432             # read file
433             #
434 5         17 my $file = $S->_mapping($csid);
435 5 50       265 return $S->_error ("Cannot find mapping file for id \"$csid\"!")
436             unless -f $file
437             ;
438 5 50       270 return $S->_error ("Cannot open binary mapping \"$file\"!")
439             if !open(MAP1, $file)
440             ;
441 5         19 binmode MAP1;
442 5         404 my $size = read MAP1, $buf, -s $file;
443 5         103 close MAP1;
444 5 50       125 return $S->_error ("Error while reading mapping \"$file\"!")
445             if ($size != -s $file)
446             ;
447              
448 5 100       13 if ($size>0x1000) {
449 3 50       9 $S->_msg("loading mapfile \"$csid\"") if $S->_noise>0;
450             } else {
451 2 50       7 $S->_msg("loading mapfile \"$csid\"") if $S->_noise>2;
452             }
453              
454 5 50       76877 return $S->_error ("Error in binary map file!\n")
455             if !$S->_read_binary_mapping($buf, 0, \%U, \%C)
456             ;
457              
458 5 100       30 if ($size>0x1000) {
459 3 50       26 $S->_msg("loaded") if $S->_noise>0;
460             } else {
461 2 50       14 $S->_msg("loaded") if $S->_noise>2;
462             }
463              
464 5         37 $mappings{$csid} = {
465             TO_CUS => \%C,
466             TO_UNI => \%U
467             };
468             # $S->_dump_TMap ($mappings{$csid});
469 5         33 1}
470              
471             sub _dump_TMap {
472 0     0   0 my ($S, $TMap) = @_;
473 0         0 print "\nDumping TMap $TMap\n";
474 0         0 my ($pat1, $pat2, $up1, $up2);
475 0         0 foreach (keys %$TMap) {
476 0         0 my $subTMap = $TMap->{$_};
477 0         0 print "SubTMap $_:\n";
478 0         0 my @n = sort {(split/,/,$b)[0] <=> (split/,/,$a)[0]} keys %$subTMap;
  0         0  
479 0         0 for (@n) {
480 0         0 my ($cs1, $n1, $cs2, $n2) = split /,/;
481 0         0 print " Submapping $cs1 bytes ($n1 times) => "
482             ."$cs2 bytes ($n2 times):\n"
483             ;
484 0         0 my $s="";
485 0         0 $pat1 = ("%0".($cs1*2)."x ") x $n1;
486 0         0 $pat2 = ("%0".($cs2*2)."x ") x $n2;
487 0         0 $up1 = ($order[0]->{$cs1}).$n1;
488 0         0 $up2 = ($order[0]->{$cs2}).$n2;
489 0         0 my $subsubTMap = $subTMap->{$_};
490 0         0 for (sort keys %$subsubTMap) {
491 0         0 printf " $pat1 => $pat2\n",
492             unpack($up1, $_),
493             unpack($up2, $subsubTMap->{$_})
494             ;
495             }
496             }
497             }
498 0         0 print "Dumping done.\n\n";
499             }
500              
501             ##
502             ## --- Text (Unicode, Keld) to IMap ---------------------------------------
503             ##
504              
505             sub _read_text_mapping {
506 0     0   0 my ($S, $id, $path, $style) = @_;
507 0 0       0 $S->_IMap({}) if !defined $S->_IMap;
508 0 0 0     0 return $S->_error("Bad charset id") if (!$id || !$registry{$id});
509 0 0 0     0 if ($style =~ /^keld$/i) {
    0          
    0          
510 0         0 $S->_read_text_keld_to_IMap($id, $path);
511             } elsif ($style =~ /^reverse$/i) {
512 0         0 $S->_read_text_unicode_to_IMap($id, $path, 2, 1);
513             } elsif (!$style || $style=~/^unicode$/i) {
514 0         0 $S->_read_text_unicode_to_IMap($id, $path, 1, 2);
515             } else {
516 0         0 my ($vendor, $unicode) = ($style =~ /^\s*(\d+)\s+(\d+)/);
517 0 0 0     0 if ($vendor && $unicode) {
518 0         0 $S->_read_text_unicode_to_IMap($id, $path, $vendor, $unicode);
519             } else {
520 0         0 return $S->_error("Unknown style '$style'");
521             }
522             }
523             }
524              
525             sub _read_text_keld_to_IMap {
526 0     0   0 my ($S, $csid, $path) = @_;
527 0         0 my %U = ();
528 0         0 my ($k, $v);
529 0         0 my $com = ""; my $esc = "";
  0         0  
530 0 0       0 return 0 unless my @file = $S -> readTextFile ( $path );
531 0         0 while ( @file ) {
532 0         0 $_ = shift ( @file );
533 0 0       0 s/$com.*// if $com;
534 0 0       0 s/^\s+//; s/\s+$//; next if !$_;
  0         0  
  0         0  
535 0 0       0 last if /^CHARMAP/i;
536 0         0 ($k, $v) = split /\s+/,$_,2;
537 0 0       0 if ($k =~ //i) { $com = $v; next }
  0         0  
  0         0  
538 0 0       0 if ($k =~ //i) { $esc = $v; next }
  0         0  
  0         0  
539             }
540 0         0 my (@l, $f, $t);
541 0         0 my $escx = $esc."x";
542 0         0 while ( @file ) {
543 0         0 $_ = shift ( @file );
544 0 0       0 s/$com.*// if $com;
545 0 0       0 next if ! /$escx([^\s]+)\s+]+)/;
546 0         0 $U{length($1)*4}->{hex($1)} = hex($2);
547             }
548             # $S->_dump_IMap(\%U);
549 0         0 $S->_IMap->{$csid} = \%U;
550 0         0 1}
551              
552             sub readTextFile {
553 3     3 0 9 my ( $S, $filePath ) = @_;
554 3         13 local $/;
555 3 50       13 return $S->_error ( "No text file specified!" ) unless $filePath;
556 3 50       166 return $S->_error ( "Can't find text file \"$filePath\"!" )
557             unless -f $filePath
558             ;
559 3 50       158 return $S->_error ( "Cannot open text file \"$filePath\"!" )
560             unless open ( FILE, $filePath )
561             ;
562 3         8 undef $/; my $file = ;
  3         302  
563 3 50       87 close FILE or warn ( "Oops: can't close file '$filePath'! ($!)" );
564 3         12904 return map "$_\n", split /\r\n|\r|\n/, $file;
565             }
566              
567             sub _read_text_unicode_to_IMap {
568             #
569             # Converts map files like created by Unicode Inc. to IMap
570             #
571 3     3   36 no strict;
  3         12  
  3         16872  
572 0     0   0 my ($S, $csid, $file, $row_vendor, $row_unicode) = @_;
573 0         0 my %U = ();
574              
575 0 0       0 return 0 unless my @file = $S -> readTextFile ( $file );
576              
577 0         0 my (@l, $f, $t);
578 0         0 my $hex = '(?:0x)?([^\s]+)\s+';
579 0         0 my $hexgap = '(?:0x)?[^\s]+\s+';
580 0         0 my ($min, $max) = ($row_vendor, $row_unicode);
581 0 0       0 ($min, $max) = ($row_unicode, $row_vendor) if $row_unicode<$row_vendor;
582 0         0 my $gap1 = $hexgap x ($min - 1);
583 0         0 my $gap2 = $hexgap x ($max - $min - 1);
584 0 0       0 if ($row_vendor > $row_unicode) {
585 0         0 $row_unicode=1; $row_vendor=2;
  0         0  
586             } else {
587 0         0 $row_unicode=2; $row_vendor=1;
  0         0  
588             }
589              
590             # Info fields in comments: (at this release still unused)
591 0         0 my $Name = "";
592 0         0 my $Unicode_version = "";
593 0         0 my $Table_version = "";
594 0         0 my $Date = "";
595 0         0 my $Authresses = "";
596              
597 0         0 my $comment_info = 1; my $comment_authress=0;
  0         0  
598 0         0 while( @file ) {
599 0         0 $_ = shift ( @file );
600 0 0 0     0 if ($comment_info && !/#/) {
601 0         0 $comment_info = 0;
602             }
603 0 0       0 if ($comment_info) {
604 0 0 0     0 if ($comment_authress && (/^#\s*$/ || /^#[^:]:/)) {
      0        
605 0         0 $comment_authress = 0;
606             }
607 0 0       0 if (/#\s*name\S*:\s*(.*$)/i) {
608 0         0 $Name = $1;
609             }
610 0 0       0 if (/#\s*unicode\s*version\S*:\s*(.*$)/i) {
611 0         0 $Unicode_version = $1;
612             }
613 0 0       0 if (/#\s*table\s*version\S*:\s*(.*$)/i) {
614 0         0 $Table_version = $1;
615             }
616 0 0       0 if (/#\s*date\S*:\s*(.*$)/i) {
617 0         0 $Date = $1;
618             }
619 0 0       0 if ($comment_authress) {
    0          
620 0 0       0 $Authresses .= ", $1" if /^#\s*(.+$)/;
621             } elsif (/#\s*Author\S*:\s*(.*$)/i) {
622 0         0 $Authresses = $1; $comment_authress=1;
  0         0  
623             }
624             }
625 0         0 s/#.*$//;
626 0 0       0 next if !$_;
627 0 0       0 next if ! /^$gap1$hex$gap2$hex/i;
628 0         0 ($f, $t) = ($$row_vendor, $$row_unicode);
629 0         0 $f =~ s/0x//ig;
630 0         0 $t =~ s/0x//ig;
631 0 0       0 if ( index($f,"+")>=0 ) {
632             # The left side contains one or more "+". Handling this way:
633             # The key becomes an 8 bit string.
634 0         0 $f =~ s/\s*\+\s*//g;
635 0         0 my $fs = pack ( "H*", $f );
636 0 0       0 if (index($t, "+")<0) {
637 0         0 my $list = "8,".length($fs);
638 0         0 $U { $list } -> { $fs } = hex ( $t );
639             } else {
640 0         0 @l = map hex($_), split /\+/, $t;
641 0         0 my $list = "8,".length($fs).",".($#l+1);
642 0         0 $U { $list } -> { $fs } = [@l];
643             }
644             } else {
645 0 0       0 if (index($t, "+")<0) {
646 0         0 $U{length($f)*4}->{hex($f)} = hex($t);
647             } else {
648 0         0 @l = map hex($_), split /\+/, $t;
649 0         0 $U{(length($f)*4).",1,".($#l+1)}->{hex($f)} = [@l];
650             }
651             }
652             }
653             # $S->_dump_IMap(\%U);
654 0         0 $S->_IMap->{$csid} = \%U;
655 0         0 1}
656              
657             sub _dump_IMap {
658             #
659             # Dump IMap
660             #
661 0     0   0 my ($S, $U) = @_;
662 0         0 print "\nDumping IMap entry.\n";
663 0         0 my ($U1, @list);
664 0         0 for (keys %{$U}) {
  0         0  
665 0         0 my $size = $_ / 4;
666 0         0 $U1 = $U->{$_};
667 0         0 for (sort {$a <=> $b} keys %{$U1}) {
  0         0  
  0         0  
668 0         0 printf ((" %0$size"."x => "), $_);
669 0 0       0 if (ref($U1->{$_})) {
670 0         0 @list = @{$U1->{$_}};
  0         0  
671 0         0 printf "(".("%04x " x ($#list+1)).")\n", @list;
672             } else {
673 0         0 printf "%04x\n", $U1->{$_};
674             }
675             }
676             }
677 0         0 1}
678              
679             ##
680             ## --- IMap to binary -----------------------------------------------------
681             ##
682              
683             sub _write_IMap_to_binary {
684 0     0   0 my ($S, $csid, $path) = @_;
685 0 0       0 return $S->_error("Integer Map \"$csid\" not loaded!\n")
686             if !(my $IMap = $S->_IMap->{$csid})
687             ;
688 0 0       0 return $S->_error("Cannot open output table \"$path\"!")
689             if !open (MAP4, ">$path");
690             ;
691 0         0 binmode MAP4;
692 0         0 my $str = "";
693 0         0 $str .= _map_binary_begin();
694 0         0 $str .= _map_binary_stream(I_NAME, $S->_to_unicode($csid));
695 0         0 $str .= _map_binary_mode(M_BYTE);
696 0         0 $str .= _map_binary_mode(M_PKV);
697 0         0 my ($from, $from_n, $to_n);
698 0         0 for (keys %{$IMap}) {
  0         0  
699 0         0 ($from, $from_n, $to_n) = split /\s*,\s*/;
700 0   0     0 my $subMapping = $S->_map_binary_submapping (
      0        
701             $IMap->{$_}, $from, $from_n||1, 16, $to_n||1
702             );
703 0 0       0 return 0 unless $subMapping;
704 0         0 $str .= $subMapping;
705             }
706 0         0 $str .= _map_binary_mode(M_END);
707 0         0 print MAP4 "$str";
708 0         0 close (MAP4);
709 0         0 1}
710              
711             sub _to_unicode {
712 0     0   0 my ($S, $txt) = @_;
713 0         0 $S -> to_unicode ($ENV{LC_CTYPE}, \$txt);
714             }
715              
716             sub _map_binary_begin {
717 0     0   0 pack($order[0]->{2}, MAGIC);
718             }
719              
720             sub _map_binary_end {
721 0     0   0 pack("C", M_END);
722             }
723              
724             sub _map_binary_submapping {
725 0     0   0 my ($S, $mapH, $size1, $n1, $size2, $n2) = @_;
726 0 0 0     0 return $S->_error ("No IMap specified!") if (!$mapH || !%$mapH);
727              
728 0 0       0 if ($n2*$size2>0xffff) {
729 0         0 return $S->_error ("Bad n character mapping! Too many chars!");
730             }
731              
732 0         0 my $bs1S = $order[0]->{int(($size1+7)/8)};
733 0         0 my $bs2S = $order[0]->{int(($size2+7)/8)}.$n2;
734 0 0       0 return $S->_error ("'From' characters have zero size!") if !$bs1S;
735              
736 0         0 my $str = "";
737 0         0 my $sig = pack ("C4", ($size1, $n1, $size2, $n2));
738            
739 0         0 my @key;
740 0 0       0 if ( $n1==1 ) {
741 0         0 @key = sort {$a <=> $b} keys %$mapH;
  0         0  
742             } else {
743 0         0 @key = sort keys %$mapH;
744             }
745 0         0 my @val = map $mapH->{$_}, @key;
746 0         0 my $max = $#key;
747              
748 0 0       0 if ($n1>1) {
749 0         0 $str .= _map_binary_mode(M_AKV);
750 0         0 $str .= _map_binary_mode(M_BYTE);
751 0         0 $str .= $sig;
752 0         0 my $n = 0;
753 0         0 while ( @key ) {
754 0 0       0 if ( $n==0 ) {
755 0         0 $n = $#key + 1;
756 0 0       0 if ( $n>255 ) {
757 0         0 $n = 255;
758             }
759 0         0 $str .= pack ( "C", $n );
760             }
761 0         0 $str .= shift ( @key );
762 0         0 my $val = shift ( @val );
763 0 0       0 if ( $n2==1 ) {
764 0         0 $str .= pack ( $bs2S, $val );
765             } else {
766 0         0 $str .= pack ( $bs2S, @$val );
767             }
768 0         0 $n--;
769             }
770             } else {
771 0         0 my ($kkey, $kbegin, $kend, $kn, $vkey, $vbegin, $vend, $vn);
772 0 0       0 if ($n2==1) {
773 0         0 $str .= _map_binary_mode(M_PKV);
774 0         0 $str .= $sig;
775 0         0 $kkey = _list_to_intervals(\@key, 0, $#key);
776 0         0 while (@$kkey) {
777 0         0 $kbegin = shift(@$kkey);
778 0         0 $kend = shift(@$kkey);
779             #print "kbegin=$kbegin kend=$kend klen=".($kend-$kbegin+1)."\n";
780 0         0 $str .= pack("C", $kend-$kbegin+1);
781 0         0 $str .= pack($bs1S, $key[$kbegin]);
782 0         0 $vkey = _list_to_intervals(\@val, $kbegin, $kend);
783 0         0 while (@$vkey) {
784 0         0 $vbegin = shift (@$vkey);
785 0         0 $vend = shift (@$vkey);
786 0         0 $str .= pack("C", $vend-$vbegin+1);
787 0         0 $str .= pack($bs2S, $val[$vbegin]);
788             }
789             }
790             } else {
791 0         0 $str .= _map_binary_mode(M_CVn);
792 0         0 $str .= $sig;
793 0         0 $kkey = _list_to_intervals(\@key, 0, $#key);
794 0         0 while (@$kkey) {
795 0         0 $kbegin = shift(@$kkey);
796 0         0 $kend = shift(@$kkey);
797 0         0 $str .= pack("C", $kend-$kbegin+1);
798 0         0 $str .= pack($bs1S, $key[$kbegin]);
799 0         0 for ($kbegin..$kend) {
800 0         0 $str .= pack($bs2S, @{$val[$_]});
  0         0  
801             }
802             }
803             }
804             }
805 0         0 $str .= _map_binary_mode(M_END);
806 0         0 $str;
807             }
808              
809             sub _map_binary_mode {
810 0     0   0 my ($mode) = @_;
811 0         0 return "\0".pack("C", $mode)."\0";
812             }
813              
814             sub _map_binary_stream {
815 0     0   0 my ($mode, $str) = @_;
816 0 0       0 if (length($str) > 255) {
817 0         0 $str = substr($str, 0, 255);
818             }
819 0         0 my $len = length($str);
820 0         0 return "\0".pack("C2", $mode, $len).$str;
821             }
822              
823             ##
824             ## --- registry file -------------------------------------------------------
825             ##
826              
827             #
828             # Registry entries:
829             # ALIAS => [a list of equivalent charset ids]
830             # INFO => some occult information about this charset
831             # MAP => the path to the binary mapfile of this charset
832             # SRC => the path to the textual mapfile of this charset
833             # SRCURL => an URL where to get the textual mapfile of this charset
834             # STYLE => describes what type of textual mapfile this is
835             #
836             # Registry example:
837             # registry = (
838             # "ISO-8859-3" => {
839             # "ALIAS" => ["ISO-IR-109","ISO_8859-3:1988","LATIN3","L3"],
840             # "INFO" => "",
841             # "MAP" => "/usr/lib/perl5/.../Unicode/Map/ISO/8859-3.map",
842             # "SRC" => "/usr/local/Unicode/ISO8859/8859-3.TXT",
843             # "SRCURL" => "ftp://ftp.unicode.org/MAPPINGS/ISO8859/8859-3.TXT",
844             # "STYLE" => "",
845             # }
846             # )
847             #
848              
849             sub _load_registry {
850             #
851             # The REGISTRY loaded once and reused later. Runtime modifications of
852             # REGISTRY will remain unnoticed!
853             #
854 9 100   9   38 return 1 if $registry_loaded;
855 3         8 my ($S) = @_;
856 3 50       9 $S->_msg("loading unicode registry") if $S->_noise>2;
857 3         15 my $path = $S -> _get_path ( "REGISTRY" );
858 3 50       19 return 0 unless my @file = $S -> readTextFile ( $path );
859              
860 3         348 my %var = ();
861 3         7 my ($k, $v);
862              
863 3         13 while ( @file ) {
864 156         256 $_ = shift ( @file );
865             # Skip everything until DEFINE marker...
866 156 100       336 s/#.*//; s/^\s+//; s/\s+$//; next if !$_;
  156         278  
  156         166  
  156         365  
867 3 50       21 last if /^DEFINE:/i;
868             }
869 3         12 while ( @file ) {
870 111         190 $_ = shift ( @file );
871 111 100       234 s/#.*//; s/^\s+//; s/\s+$//; next if !$_;
  111         230  
  111         243  
  111         288  
872 21 100       52 last if /^DATA:/i;
873 18         78 ($k, $v) = split /\s*[= ]\s*/,$_,2;
874 18         31 $k=~s/^\$//; $v=~s/^"(.*)"$/$1/;
  18         84  
875 18 50       56 if ( defined $ENV{$k} ) {
876             # User environment overrides file settings.
877 0         0 $v = $ENV { $k };
878             } else {
879 18 50       123 if ($v!~s/^'(.*)'$/$1/) {
880 18         21 my @check;
881             # parse environment
882 18         28 @check=(); while ($v=~/\$(\w+|\$)/g) { push (@check, $1) }
  18         65  
  9         36  
883 18         33 for (@check) {
884 9 50       45 if ( defined $ENV{$_} ) {
    100          
    50          
885             # User environment has ranges before registry and magics.
886 0         0 $v =~ s/\$$_/$ENV{$_}/g
887             } elsif ( $_ eq '$' ) {
888             # Magic value $$
889 3         42 $v =~ s/\$\$/$MAP_Path/;
890             } elsif ( defined $var{$_} ) {
891             # Apply registry variables
892 6         171 $v =~ s/\$$_/$var{$_}/g
893             } else {
894             # Error, undefined value!
895 0         0 warn ("Error in file REGISTRY: Variable '$_' not defined!");
896 0         0 return 0;
897             }
898             }
899             # parse home tilde
900 18 100 66     567 if (($v eq '~') || ($v=~/^~\//)) {
901 3         17 $v =~ s/^~/_getHomeDir()/e;
  3         15  
902             }
903             }
904             }
905 18         64 $var{$k} = $v;
906             }
907 3         8 my ($name, $map, $src, $srcURL, $style, @alias, $info);
908 3         26 my %arg_s = (
909             "name"=>\$name, "map"=>\$map, "src"=>\$src, "srcurl"=>\$srcURL,
910             "style"=>\$style, "info"=>\$info
911             );
912 3         13 my %arg_a = ("alias"=>\@alias);
913 3         9 $name=""; $map=""; $src=""; $srcURL=""; $style=""; @alias=(); $info="";
  3         6  
  3         4  
  3         6  
  3         6  
  3         7  
  3         8  
914 3         13 while ( @file ) {
915 2250         3204 $_ = shift ( @file );
916 2250         4060 s/#.*//; s/^\s+//; s/\s+$//;
  2250         13311  
  2250         5711  
917 2250 100       4457 if (!$_) {
918 699 100       1589 $S->_add_registry_entry (
919             $name, $src, $map, $srcURL, $style, \@alias, $info
920             ) if $name;
921 699         973 $name=""; $map=""; $src=""; $srcURL=""; $style=""; @alias=();
  699         2251  
  699         645  
  699         653  
  699         683  
  699         929  
922 699         2025 $info=""; next;
  699         1515  
923             }
924 1551         10414 ($k, $v) = split /\s*[: ]\s*/,$_,2;
925 1551         4011 for (keys %var) {
926 9306         78720 $v =~ s/\$$_/$var{$_}/g;
927             }
928 1551         3146 $k = lc($k);
929 1551 100       3595 if ($arg_s{$k}) {
    50          
930 1092         1053 ${$arg_s{$k}} = $v;
  1092         3208  
931             } elsif ($arg_a{$k}) {
932 459         428 push (@{$arg_a{$k}}, $v);
  459         1484  
933             }
934             }
935 3 50       17 $S->_msg_fin("done") if $S->_noise>2;
936 3         8 $registry_loaded=1;
937 3         37 1}
938              
939             sub _getHomeDir {
940 3 50 33 3   34 $ENV{HOME}
941             || eval ( '(getpwuid($<))[7]' ) # for systems not supporting getpwuid
942             || "/";
943             }
944              
945             sub _add_registry_entry {
946 273     273   581 my ($S, $name, $src, $map, $srcURL, $style, $aliasL, $info) = @_;
947 273 100 100     4949 $registry{$name} = {
      50        
      50        
      100        
      100        
948             "ALIAS" => $aliasL ? [@$aliasL] : [],
949             "MAP" => $map || "",
950             "INFO" => $info || "",
951             "SRC" => $src || "",
952             "SRCURL" => $srcURL || "",
953             "STYLE" => $style || "",
954             };
955             }
956              
957             sub _dump_registry {
958 0     0   0 my ($k, $v);
959 0         0 print "\nDumping registry definition:\n";
960 0         0 while (($k, $v) = each %registry) {
961 0         0 print "Name: $k\n";
962 0         0 printf " src: %s\n", $v->{"SRC"};
963 0         0 printf " srcURL: %s\n", $v->{"SRC"};
964 0         0 printf " style: %s\n", $v->{"STYLE"};
965 0         0 printf " map: %s\n", $v->{"MAP"};
966 0         0 printf " info: %s\n", $v->{"INFO"};
967 0         0 print " alias: " . join (", ", @{$v->{"ALIAS"}}) . "\n";
  0         0  
968 0         0 print "\n";
969             }
970 0         0 print "done.\n";
971             }
972              
973             ##
974             ## --- misc ---------------------------------------------------------------
975             ##
976              
977             sub _get_path {
978 3     3   9 my ($S, $path) = @_;
979 3 50       11 return $S->_error("Cannot find mapfile base directory!") if !$MAP_Path;
980 3         192 $path =~ s/^\/+//;
981 3         14 return "$MAP_Path/$path";
982             }
983              
984             sub _list_to_intervals {
985 0     0   0 my ($listR, $start, $end) = @_;
986 0         0 my @split = ();
987 0         0 my ($begin, $i, $partend);
988 0         0 $i=$start;
989 0         0 while ($i<=$end) {
990 0         0 $begin = $i;
991 0         0 $partend = $begin+254;
992 0   0     0 while (
      0        
993             ($i<$end) &&
994             ($i<$partend) &&
995             ($listR->[$i+1]==($listR->[$i]+1))
996             ) {
997 0         0 $i++
998             }
999 0         0 push (@split, ($begin, $i));
1000 0         0 $i++;
1001             }
1002 0         0 \@split;
1003             }
1004              
1005             sub _deprecated {
1006 13     13   21 my ( $msg ) = @_;
1007 13 100       34 if ( $WARNINGS & WARN_DEPRECATION ) {
1008 7         8 my $s = "Deprecated usage!";
1009 7 100       18 $s .= " ($msg)" if $msg;
1010 7         991 carp ( $s );
1011             }
1012 13         290 1}
1013              
1014             sub _incompatible {
1015 6     6   8 my ( $msg ) = @_;
1016 6 50       16 if ( $WARNINGS & WARN_COMPATIBILITY ) {
1017 0         0 my $s = "Incompatible usage!";
1018 0 0       0 $s .= " ($msg)" if $msg;
1019 0         0 carp ( $s );
1020             }
1021 6         9 1}
1022              
1023             "Atomkraft? Nein, danke!"
1024              
1025             __END__