File Coverage

blib/lib/Unicode/Collate.pm
Criterion Covered Total %
statement 504 516 97.6
branch 286 316 90.5
condition 175 200 87.5
subroutine 59 59 100.0
pod 19 29 65.5
total 1043 1120 93.1


line stmt bran cond sub pod time code
1             package Unicode::Collate;
2              
3             BEGIN {
4 138     138   41147 unless ("A" eq pack('U', 0x41)) {
5             die "Unicode::Collate cannot stringify a Unicode code point\n";
6             }
7 138 50       4274 unless (0x41 == unpack('U', 'A')) {
8 0         0 die "Unicode::Collate cannot get a Unicode code point\n";
9             }
10             }
11              
12 138     138   3549 use 5.006;
  138         492  
13 138     138   841 use strict;
  138         318  
  138         4186  
14 138     138   812 use warnings;
  138         348  
  138         4395  
15 138     138   825 use Carp;
  138         301  
  138         10184  
16 138     138   940 use File::Spec;
  138         399  
  138         4305  
17              
18 138     138   800 no warnings 'utf8';
  138         351  
  138         10389  
19              
20             our $VERSION = '1.30';
21             our $PACKAGE = __PACKAGE__;
22              
23             ### begin XS only ###
24 138     138   1066 use XSLoader ();
  138         392  
  138         9909  
25             XSLoader::load('Unicode::Collate', $VERSION);
26             ### end XS only ###
27              
28             my @Path = qw(Unicode Collate);
29             my $KeyFile = 'allkeys.txt';
30              
31             # Perl's boolean
32 138     138   929 use constant TRUE => 1;
  138         312  
  138         18066  
33 138     138   957 use constant FALSE => "";
  138         302  
  138         8194  
34 138     138   1086 use constant NOMATCHPOS => -1;
  138         344  
  138         8713  
35              
36             # A coderef to get combining class imported from Unicode::Normalize
37             # (i.e. \&Unicode::Normalize::getCombinClass).
38             # This is also used as a HAS_UNICODE_NORMALIZE flag.
39             my $CVgetCombinClass;
40              
41             # Supported Levels
42 138     138   920 use constant MinLevel => 1;
  138         284  
  138         7653  
43 138     138   830 use constant MaxLevel => 4;
  138         320  
  138         7025  
44              
45             # Minimum weights at level 2 and 3, respectively
46 138     138   884 use constant Min2Wt => 0x20;
  138         287  
  138         7443  
47 138     138   897 use constant Min3Wt => 0x02;
  138         278  
  138         8916  
48              
49             # Shifted weight at 4th level
50 138     138   923 use constant Shift4Wt => 0xFFFF;
  138         335  
  138         7290  
51              
52             # A boolean for Variable and 16-bit weights at 4 levels of Collation Element
53 138     138   916 use constant VCE_TEMPLATE => 'Cn4';
  138         304  
  138         8052  
54              
55             # A sort key: 16-bit weights
56 138     138   961 use constant KEY_TEMPLATE => 'n*';
  138         319  
  138         7578  
57              
58             # The tie-breaking: 32-bit weights
59 138     138   2870 use constant TIE_TEMPLATE => 'N*';
  138         431  
  138         9626  
60              
61             # Level separator in a sort key:
62             # i.e. pack(KEY_TEMPLATE, 0)
63 138     138   942 use constant LEVEL_SEP => "\0\0";
  138         317  
  138         7606  
64              
65             # As Unicode code point separator for hash keys.
66             # A joined code point string (denoted by JCPS below)
67             # like "65;768" is used for internal processing
68             # instead of Perl's Unicode string like "\x41\x{300}",
69             # as the native code point is different from the Unicode code point
70             # on EBCDIC platform.
71             # This character must not be included in any stringified
72             # representation of an integer.
73 138     138   875 use constant CODE_SEP => ';';
  138         642  
  138         8034  
74             # NOTE: in regex /;/ is used for $jcps!
75              
76             # boolean values of variable weights
77 138     138   886 use constant NON_VAR => 0; # Non-Variable character
  138         296  
  138         7604  
78 138     138   1575 use constant VAR => 1; # Variable character
  138         441  
  138         8832  
79              
80             # specific code points
81 138     138   957 use constant Hangul_SIni => 0xAC00;
  138         291  
  138         7009  
82 138     138   944 use constant Hangul_SFin => 0xD7A3;
  138         303  
  138         1040608  
83              
84             # Logical_Order_Exception in PropList.txt
85             my $DefaultRearrange = [ 0x0E40..0x0E44, 0x0EC0..0x0EC4 ];
86              
87             # for highestFFFF and minimalFFFE
88             my $HighestVCE = pack(VCE_TEMPLATE, 0, 0xFFFE, 0x20, 0x5, 0xFFFF);
89             my $minimalVCE = pack(VCE_TEMPLATE, 0, 1, 0x20, 0x5, 0xFFFE);
90              
91 200     200 1 906 sub UCA_Version { '43' }
92              
93 10     10 1 38 sub Base_Unicode_Version { '13.0.0' }
94              
95             ######
96              
97             sub pack_U {
98 1706     1706 0 5149 return pack('U*', @_);
99             }
100              
101             sub unpack_U {
102 59438     59438 0 208424 return unpack('U*', shift(@_).pack('U*'));
103             }
104              
105             ######
106              
107             my (%VariableOK);
108             @VariableOK{ qw/
109             blanked non-ignorable shifted shift-trimmed
110             / } = (); # keys lowercased
111              
112             our @ChangeOK = qw/
113             alternate backwards level normalization rearrange
114             katakana_before_hiragana upper_before_lower ignore_level2
115             overrideCJK overrideHangul overrideOut preprocess UCA_Version
116             hangul_terminator variable identical highestFFFF minimalFFFE
117             long_contraction
118             /;
119              
120             our @ChangeNG = qw/
121             entry mapping table maxlength contraction
122             ignoreChar ignoreName undefChar undefName rewrite
123             versionTable alternateTable backwardsTable forwardsTable
124             rearrangeTable variableTable
125             derivCode normCode rearrangeHash backwardsFlag
126             suppress suppressHash
127             __useXS /; ### XS only
128             # The hash key 'ignored' was deleted at v 0.21.
129             # The hash key 'isShift' was deleted at v 0.23.
130             # The hash key 'combining' was deleted at v 0.24.
131             # The hash key 'entries' was deleted at v 0.30.
132             # The hash key 'L3_ignorable' was deleted at v 0.40.
133              
134             sub version {
135 8     8 1 106 my $self = shift;
136 8   100     42 return $self->{versionTable} || 'unknown';
137             }
138              
139             my (%ChangeOK, %ChangeNG);
140             @ChangeOK{ @ChangeOK } = ();
141             @ChangeNG{ @ChangeNG } = ();
142              
143             sub change {
144 823     823 1 21605 my $self = shift;
145 823         3000 my %hash = @_;
146 823         1486 my %old;
147 823 100       2619 if (exists $hash{alternate}) {
148 9 100       30 if (exists $hash{variable}) {
149 1         4 delete $hash{alternate};
150             } else {
151 8         21 $hash{variable} = $hash{alternate};
152             }
153             }
154 823         2988 foreach my $k (keys %hash) {
155 884 50       2484 if (exists $ChangeOK{$k}) {
    0          
156 884         2049 $old{$k} = $self->{$k};
157 884         2098 $self->{$k} = $hash{$k};
158             } elsif (exists $ChangeNG{$k}) {
159 0         0 croak "change of $k via change() is not allowed!";
160             }
161             # else => ignored
162             }
163 823         2861 $self->checkCollator();
164 823 100       3158 return wantarray ? %old : $self;
165             }
166              
167             sub _checkLevel {
168 1059     1059   2169 my $level = shift;
169 1059         1794 my $key = shift; # 'level' or 'backwards'
170 1059 50       2754 MinLevel <= $level or croak sprintf
171             "Illegal level %d (in value for key '%s') lower than %d.",
172             $level, $key, MinLevel;
173 1059 50       3167 $level <= MaxLevel or croak sprintf
174             "Unsupported level %d (in value for key '%s') higher than %d.",
175             $level, $key, MaxLevel;
176             }
177              
178             my %DerivCode = (
179             8 => \&_derivCE_8,
180             9 => \&_derivCE_9,
181             11 => \&_derivCE_9, # 11 == 9
182             14 => \&_derivCE_14,
183             16 => \&_derivCE_14, # 16 == 14
184             18 => \&_derivCE_18,
185             20 => \&_derivCE_20,
186             22 => \&_derivCE_22,
187             24 => \&_derivCE_24,
188             26 => \&_derivCE_24, # 26 == 24
189             28 => \&_derivCE_24, # 28 == 24
190             30 => \&_derivCE_24, # 30 == 24
191             32 => \&_derivCE_32,
192             34 => \&_derivCE_34,
193             36 => \&_derivCE_36,
194             38 => \&_derivCE_38,
195             40 => \&_derivCE_40,
196             41 => \&_derivCE_40, # 41 == 40
197             43 => \&_derivCE_43,
198             );
199              
200             sub checkCollator {
201 1043     1043 0 1820 my $self = shift;
202 1043         4079 _checkLevel($self->{level}, 'level');
203              
204             $self->{derivCode} = $DerivCode{ $self->{UCA_Version} }
205 1043 50       4020 or croak "Illegal UCA version (passed $self->{UCA_Version}).";
206              
207             $self->{variable} ||= $self->{alternate} || $self->{variableTable} ||
208 1043   50     5172 $self->{alternateTable} || 'shifted';
      66        
209 1043         3374 $self->{variable} = $self->{alternate} = lc($self->{variable});
210             exists $VariableOK{ $self->{variable} }
211 1043 50       3255 or croak "$PACKAGE unknown variable parameter name: $self->{variable}";
212              
213 1043 100       2985 if (! defined $self->{backwards}) {
    100          
214 919         2202 $self->{backwardsFlag} = 0;
215             } elsif (! ref $self->{backwards}) {
216 11         53 _checkLevel($self->{backwards}, 'backwards');
217 11         36 $self->{backwardsFlag} = 1 << $self->{backwards};
218             } else {
219 113         146 my %level;
220 113         217 $self->{backwardsFlag} = 0;
221 113         140 for my $b (@{ $self->{backwards} }) {
  113         221  
222 5         13 _checkLevel($b, 'backwards');
223 5         18 $level{$b} = 1;
224             }
225 113         286 for my $v (sort keys %level) {
226 5         22 $self->{backwardsFlag} += 1 << $v;
227             }
228             }
229              
230 1043 100       2658 defined $self->{rearrange} or $self->{rearrange} = [];
231             ref $self->{rearrange}
232 1043 50       3407 or croak "$PACKAGE: list for rearrangement must be store in ARRAYREF";
233              
234             # keys of $self->{rearrangeHash} are $self->{rearrange}.
235 1043         1934 $self->{rearrangeHash} = undef;
236              
237 1043 100       1544 if (@{ $self->{rearrange} }) {
  1043         2863  
238 13         20 @{ $self->{rearrangeHash} }{ @{ $self->{rearrange} } } = ();
  13         107  
  13         40  
239             }
240              
241 1043         2003 $self->{normCode} = undef;
242              
243 1043 100       2576 if (defined $self->{normalization}) {
244 40         64 eval { require Unicode::Normalize };
  40         242  
245 40 50       90 $@ and croak "Unicode::Normalize is required to normalize strings";
246              
247 40   100     116 $CVgetCombinClass ||= \&Unicode::Normalize::getCombinClass;
248              
249 40 100       175 if ($self->{normalization} =~ /^(?:NF)D\z/) { # tweak for default
    100          
250 32         78 $self->{normCode} = \&Unicode::Normalize::NFD;
251             }
252             elsif ($self->{normalization} ne 'prenormalized') {
253 7         14 my $norm = $self->{normalization};
254             $self->{normCode} = sub {
255 147     147   1286 Unicode::Normalize::normalize($norm, shift);
256 7         29 };
257 7         42 eval { $self->{normCode}->("") }; # try
  7         18  
258 7 50       80 $@ and croak "$PACKAGE unknown normalization form name: $norm";
259             }
260             }
261 1043         1897 return;
262             }
263              
264             sub new
265             {
266 220     220 0 15868 my $class = shift;
267 220         1285 my $self = bless { @_ }, $class;
268              
269             ### begin XS only ###
270 220 100 33     3504 if (! exists $self->{table} && !defined $self->{rewrite} &&
271             !defined $self->{undefName} && !defined $self->{ignoreName} &&
272             !defined $self->{undefChar} && !defined $self->{ignoreChar}) {
273 127         803 $self->{__useXS} = \&_fetch_simple;
274             } else {
275 93         243 $self->{__useXS} = undef;
276             }
277             ### end XS only ###
278              
279             # keys of $self->{suppressHash} are $self->{suppress}.
280 220 100 66     1030 if ($self->{suppress} && @{ $self->{suppress} }) {
  6         28  
281 6         15 @{ $self->{suppressHash} }{ @{ $self->{suppress} } } = ();
  6         302  
  6         17  
282             } # before read_table()
283              
284             # If undef is passed explicitly, no file is read.
285 220 100       918 $self->{table} = $KeyFile if ! exists $self->{table};
286 220 100       1560 $self->read_table() if defined $self->{table};
287              
288 220 100       1053 if ($self->{entry}) {
289 151         1401 while ($self->{entry} =~ /([^\n]+)/g) {
290 29683         60952 $self->parseEntry($1, TRUE);
291             }
292             }
293              
294             # only in new(), not in change()
295 220   100     1699 $self->{level} ||= MaxLevel;
296 220   66     1696 $self->{UCA_Version} ||= UCA_Version();
297              
298             $self->{overrideHangul} = FALSE
299 220 100       1148 if ! exists $self->{overrideHangul};
300             $self->{overrideCJK} = FALSE
301 220 100       1046 if ! exists $self->{overrideCJK};
302             $self->{normalization} = 'NFD'
303 220 100       848 if ! exists $self->{normalization};
304             $self->{rearrange} = $self->{rearrangeTable} ||
305             ($self->{UCA_Version} <= 11 ? $DefaultRearrange : [])
306 220 100 33     2926 if ! exists $self->{rearrange};
307             $self->{backwards} = $self->{backwardsTable}
308 220 100       1289 if ! exists $self->{backwards};
309             exists $self->{long_contraction} or $self->{long_contraction}
310 220 50 100     1876 = 22 <= $self->{UCA_Version} && $self->{UCA_Version} <= 24;
311              
312             # checkCollator() will be called in change()
313 220         2289 $self->checkCollator();
314              
315 220         2135 return $self;
316             }
317              
318             sub parseAtmark {
319 684     684 0 4064 my $self = shift;
320 684         1285 my $line = shift; # after s/^\s*\@//
321              
322 684 100       4179 if ($line =~ /^version\s*(\S*)/) {
    50          
    50          
    50          
    50          
    50          
323 172   33     1927 $self->{versionTable} ||= $1;
324             }
325             elsif ($line =~ /^variable\s+(\S*)/) { # since UTS #10-9
326 0   0     0 $self->{variableTable} ||= $1;
327             }
328             elsif ($line =~ /^alternate\s+(\S*)/) { # till UTS #10-8
329 0   0     0 $self->{alternateTable} ||= $1;
330             }
331             elsif ($line =~ /^backwards\s+(\S*)/) {
332 0         0 push @{ $self->{backwardsTable} }, $1;
  0         0  
333             }
334             elsif ($line =~ /^forwards\s+(\S*)/) { # perhaps no use
335 0         0 push @{ $self->{forwardsTable} }, $1;
  0         0  
336             }
337             elsif ($line =~ /^rearrange\s+(.*)/) { # (\S*) is NG
338 0         0 push @{ $self->{rearrangeTable} }, _getHexArray($1);
  0         0  
339             }
340             }
341              
342             sub read_table {
343 172     172 0 407 my $self = shift;
344              
345             ### begin XS only ###
346 172 100       825 if ($self->{__useXS}) {
347 127         33526 my @rest = _fetch_rest(); # complex matter need to parse
348 127         1143 for my $line (@rest) {
349 119888 50       251891 next if $line =~ /^\s*#/;
350              
351 119888 100       213773 if ($line =~ s/^\s*\@//) {
352 635         2106 $self->parseAtmark($line);
353             } else {
354 119253         214035 $self->parseEntry($line);
355             }
356             }
357 127         7798 return;
358             }
359             ### end XS only ###
360              
361 45         110 my($f, $fh);
362 45         147 foreach my $d (@INC) {
363 90         1647 $f = File::Spec->catfile($d, @Path, $self->{table});
364 90 100       4344 last if open($fh, $f);
365 45         228 $f = undef;
366             }
367 45 50       249 if (!defined $f) {
368 0         0 $f = File::Spec->catfile(@Path, $self->{table});
369 0         0 croak("$PACKAGE: Can't locate $f in \@INC (\@INC contains: @INC)");
370             }
371              
372 45         2103 while (my $line = <$fh>) {
373 71112 100       187628 next if $line =~ /^\s*#/;
374              
375 70303 100       137200 if ($line =~ s/^\s*\@//) {
376 49         218 $self->parseAtmark($line);
377             } else {
378 70254         128042 $self->parseEntry($line);
379             }
380             }
381 45         1132 close $fh;
382             }
383              
384              
385             ##
386             ## get $line, parse it, and write an entry in $self
387             ##
388             sub parseEntry
389             {
390 219190     219190 0 303601 my $self = shift;
391 219190         342427 my $line = shift;
392 219190         273093 my $tailoring = shift;
393 219190         308200 my($name, $entry, @uv, @key);
394              
395 219190 100       397493 if (defined $self->{rewrite}) {
396 34764         66463 $line = $self->{rewrite}->($line);
397             }
398              
399 219190 100       698596 return if $line !~ /^\s*[0-9A-Fa-f]/;
400              
401             # removes comment and gets name
402 219089 100       678339 $name = $1
403             if $line =~ s/[#%]\s*(.*)//;
404 219089 100 100     458384 return if defined $self->{undefName} && $name =~ /$self->{undefName}/;
405              
406             # gets element
407 218665         574642 my($e, $k) = split /;/, $line;
408 218665 50       414937 croak "Wrong Entry: must be separated by ';' from "
409             if ! $k;
410              
411 218665         490674 @uv = _getHexArray($e);
412 218665 50       390414 return if !@uv;
413             return if @uv > 1 && $self->{suppressHash} && !$tailoring &&
414 218665 100 100     573865 exists $self->{suppressHash}{$uv[0]};
      100        
      100        
415 218655         473681 $entry = join(CODE_SEP, @uv); # in JCPS
416              
417 218655 100 100     645821 if (defined $self->{undefChar} || defined $self->{ignoreChar}) {
418 1690         2722 my $ele = pack_U(@uv);
419              
420             # regarded as if it were not stored in the table
421             return
422 1690 100 100     6346 if defined $self->{undefChar} && $ele =~ /$self->{undefChar}/;
423              
424             # replaced as completely ignorable
425             $k = '[.0000.0000.0000.0000]'
426 1686 100 100     6296 if defined $self->{ignoreChar} && $ele =~ /$self->{ignoreChar}/;
427             }
428              
429             # replaced as completely ignorable
430             $k = '[.0000.0000.0000.0000]'
431 218651 100 100     407001 if defined $self->{ignoreName} && $name =~ /$self->{ignoreName}/;
432              
433 218651         294540 my $is_L3_ignorable = TRUE;
434              
435 218651         979242 foreach my $arr ($k =~ /\[([^\[\]]+)\]/g) { # SPACEs allowed
436 355577         607734 my $var = $arr =~ /\*/; # exactly /^\*/ but be lenient.
437 355577         815946 my @wt = _getHexArray($arr);
438 355577         940310 push @key, pack(VCE_TEMPLATE, $var, @wt);
439 355577 100 100     893926 $is_L3_ignorable = FALSE
      100        
440             if $wt[0] || $wt[1] || $wt[2];
441             # Conformance Test for 3.1.1 and 4.0.0 shows Level 3 ignorable
442             # is completely ignorable.
443             # For expansion, an entry $is_L3_ignorable
444             # if and only if "all" CEs are [.0000.0000.0000].
445             }
446              
447             # mapping: be an array ref or not exists (any false value is disallowed)
448 218651 100       713173 $self->{mapping}{$entry} = $is_L3_ignorable ? [] : \@key;
449              
450             # maxlength: be more than 1 or not exists (any false value is disallowed)
451 218651 100       432548 if (@uv > 1) {
452 127526 100 100     433140 if (!$self->{maxlength}{$uv[0]} || $self->{maxlength}{$uv[0]} < @uv) {
453 10875         26341 $self->{maxlength}{$uv[0]} = @uv;
454             }
455             }
456              
457             # contraction: be 1 or not exists (any false value is disallowed)
458 218651         781002 while (@uv > 2) {
459 2171         3291 pop @uv;
460 2171         4715 my $fake_entry = join(CODE_SEP, @uv); # in JCPS
461 2171         12005 $self->{contraction}{$fake_entry} = 1;
462             }
463             }
464              
465              
466             sub viewSortKey
467             {
468 258     258 1 547 my $self = shift;
469 258         372 my $str = shift;
470 258         597 $self->visualizeSortKey($self->getSortKey($str));
471             }
472              
473              
474             sub process
475             {
476 59153     59153 0 78621 my $self = shift;
477 59153         79949 my $str = shift;
478 59153         94151 my $prep = $self->{preprocess};
479 59153         84280 my $norm = $self->{normCode};
480              
481 59153 100       110920 $str = &$prep($str) if ref $prep;
482 59153 100       95655 $str = &$norm($str) if ref $norm;
483 59153         105818 return $str;
484             }
485              
486             ##
487             ## arrayref of JCPS = splitEnt(string to be collated)
488             ## arrayref of arrayref[JCPS, ini_pos, fin_pos] = splitEnt(string, TRUE)
489             ##
490             sub splitEnt
491             {
492 59392     59392 0 85177 my $self = shift;
493 59392         79345 my $str = shift;
494 59392         75921 my $wLen = shift; # with Length
495              
496 59392         88726 my $map = $self->{mapping};
497 59392         80414 my $max = $self->{maxlength};
498 59392         81007 my $reH = $self->{rearrangeHash};
499 59392         86317 my $vers = $self->{UCA_Version};
500 59392   100     169457 my $ver9 = $vers >= 9 && $vers <= 11;
501 59392         89955 my $long = $self->{long_contraction};
502 59392         81113 my $uXS = $self->{__useXS}; ### XS only
503              
504 59392         78994 my @buf;
505              
506             # get array of Unicode code point of string.
507 59392         99615 my @src = unpack_U($str);
508              
509             # rearrangement:
510             # Character positions are not kept if rearranged,
511             # then neglected if $wLen is true.
512 59392 100 100     125991 if ($reH && ! $wLen) {
513 132         297 for (my $i = 0; $i < @src; $i++) {
514 180 100 100     637 if (exists $reH->{ $src[$i] } && $i + 1 < @src) {
515 13         34 ($src[$i], $src[$i+1]) = ($src[$i+1], $src[$i]);
516 13         36 $i++;
517             }
518             }
519             }
520              
521             # remove a code point marked as a completely ignorable.
522 59392         129409 for (my $i = 0; $i < @src; $i++) {
523 84694 100 100     272644 if ($vers <= 20 && _isIllegal($src[$i])) {
    100          
524 75         164 $src[$i] = undef;
525             } elsif ($ver9) {
526             $src[$i] = undef if exists $map->{ $src[$i] }
527 1470 100 66     5456 ? @{ $map->{ $src[$i] } } == 0
  337 100       976  
528             : $uXS && _ignorable_simple($src[$i]); ### XS only
529             }
530             }
531              
532 59392         117194 for (my $i = 0; $i < @src; $i++) {
533 73218         110100 my $jcps = $src[$i];
534              
535             # skip removed code point
536 73218 100       130937 if (! defined $jcps) {
537 197 100 66     374 if ($wLen && @buf) {
538 15         38 $buf[-1][2] = $i + 1;
539             }
540 197         410 next;
541             }
542              
543 73021         94537 my $i_orig = $i;
544              
545             # find contraction
546 73021 100       152420 if (exists $max->{$jcps}) {
547 13226         18633 my $temp_jcps = $jcps;
548 13226         17123 my $jcpsLen = 1;
549 13226         19479 my $maxLen = $max->{$jcps};
550              
551 13226   100     43893 for (my $p = $i + 1; $jcpsLen < $maxLen && $p < @src; $p++) {
552 13298 100       24376 next if ! defined $src[$p];
553 13272         25551 $temp_jcps .= CODE_SEP . $src[$p];
554 13272         17022 $jcpsLen++;
555 13272 100       31274 if (exists $map->{$temp_jcps}) {
556 10756         15421 $jcps = $temp_jcps;
557 10756         24990 $i = $p;
558             }
559             }
560              
561             # discontiguous contraction with Combining Char (cf. UTS#10, S2.1).
562             # This process requires Unicode::Normalize.
563             # If "normalization" is undef, here should be skipped *always*
564             # (in spite of bool value of $CVgetCombinClass),
565             # since canonical ordering cannot be expected.
566             # Blocked combining character should not be contracted.
567              
568             # $self->{normCode} is false in the case of "prenormalized".
569 13226 100       27072 if ($self->{normalization}) {
570 165         270 my $cont = $self->{contraction};
571 165         219 my $preCC = 0;
572 165         201 my $preCC_uc = 0;
573 165         225 my $jcps_uc = $jcps;
574 165         230 my(@out, @out_uc);
575              
576 165         333 for (my $p = $i + 1; $p < @src; $p++) {
577 268 100       478 next if ! defined $src[$p];
578 252         444 my $curCC = $CVgetCombinClass->($src[$p]);
579 252 100       414 last unless $curCC;
580 219         357 my $tail = CODE_SEP . $src[$p];
581              
582 219 100 100     676 if ($preCC != $curCC && exists $map->{$jcps.$tail}) {
583 64         93 $jcps .= $tail;
584 64         102 push @out, $p;
585             } else {
586 155         234 $preCC = $curCC;
587             }
588              
589 219 100       442 next if !$long;
590              
591 119 100 100     450 if ($preCC_uc != $curCC &&
      66        
592             (exists $map->{$jcps_uc.$tail} ||
593             exists $cont->{$jcps_uc.$tail})) {
594 79         104 $jcps_uc .= $tail;
595 79         184 push @out_uc, $p;
596             } else {
597 40         111 $preCC_uc = $curCC;
598             }
599             }
600              
601 165 100 100     391 if (@out_uc && exists $map->{$jcps_uc}) {
602 39         71 $jcps = $jcps_uc;
603 39         117 $src[$_] = undef for @out_uc;
604             } else {
605 126         290 $src[$_] = undef for @out;
606             }
607             }
608             }
609              
610             # skip completely ignorable
611 73021 100 100     272000 if (exists $map->{$jcps} ? @{ $map->{$jcps} } == 0 :
  33130 100       71886  
612             $uXS && $jcps !~ /;/ && _ignorable_simple($jcps)) { ### XS only
613 334 100 100     847 if ($wLen && @buf) {
614 94         147 $buf[-1][2] = $i + 1;
615             }
616 334         768 next;
617             }
618              
619 72687 100       225196 push @buf, $wLen ? [$jcps, $i_orig, $i + 1] : $jcps;
620             }
621 59392         136165 return \@buf;
622             }
623              
624             ##
625             ## VCE = _pack_override(input, codepoint, derivCode)
626             ##
627             sub _pack_override ($$$) {
628 12423     12423   19946 my $r = shift;
629 12423         16919 my $u = shift;
630 12423         17085 my $der = shift;
631              
632 12423 100       25438 if (ref $r) {
    100          
633 1343         5722 return pack(VCE_TEMPLATE, NON_VAR, @$r);
634             } elsif (defined $r) {
635 8814         41535 return pack(VCE_TEMPLATE, NON_VAR, $r, Min2Wt, Min3Wt, $u);
636             } else {
637 2266 100       4382 $u = 0xFFFD if 0x10FFFF < $u;
638 2266         11918 return $der->($u);
639             }
640             }
641              
642             ##
643             ## list of VCE = getWt(JCPS)
644             ##
645             sub getWt
646             {
647 72483     72483 0 101489 my $self = shift;
648 72483         101310 my $u = shift;
649 72483         103774 my $map = $self->{mapping};
650 72483         97618 my $der = $self->{derivCode};
651 72483         96932 my $out = $self->{overrideOut};
652 72483         100451 my $uXS = $self->{__useXS}; ### XS only
653              
654 72483 50       129075 return if !defined $u;
655 72483 100 100     135242 return $self->varCE($HighestVCE) if $u eq 0xFFFF && $self->{highestFFFF};
656 72351 100 100     120485 return $self->varCE($minimalVCE) if $u eq 0xFFFE && $self->{minimalFFFE};
657 72329 100 100     249541 $u = 0xFFFD if $u !~ /;/ && 0x10FFFF < $u && !$out;
      100        
658              
659 72329         96567 my @ce;
660 72329 100 100     228056 if (exists $map->{$u}) {
    100 100        
    100 100        
    100          
661 32644         41764 @ce = @{ $map->{$u} }; # $u may be a contraction
  32644         76650  
662             ### begin XS only ###
663             } elsif ($uXS && _exists_simple($u)) {
664 16698         44496 @ce = _fetch_simple($u);
665             ### end XS only ###
666             } elsif (Hangul_SIni <= $u && $u <= Hangul_SFin) {
667 138         230 my $hang = $self->{overrideHangul};
668 138 100       363 if ($hang) {
    100          
669 23         50 @ce = map _pack_override($_, $u, $der), $hang->($u);
670             } elsif (!defined $hang) {
671 10         32 @ce = $der->($u);
672             } else {
673 105         543 my $max = $self->{maxlength};
674 105         300 my @decH = _decompHangul($u);
675              
676 105 100       223 if (@decH == 2) {
677 45         142 my $contract = join(CODE_SEP, @decH);
678 45 100       142 @decH = ($contract) if exists $map->{$contract};
679             } else { # must be <@decH == 3>
680 60 100       225 if (exists $max->{$decH[0]}) {
681 7         21 my $contract = join(CODE_SEP, @decH);
682 7 100       21 if (exists $map->{$contract}) {
683 1         3 @decH = ($contract);
684             } else {
685 6         20 $contract = join(CODE_SEP, @decH[0,1]);
686 6 100       35 exists $map->{$contract} and @decH = ($contract, $decH[2]);
687             }
688             # even if V's ignorable, LT contraction is not supported.
689             # If such a situation were required, NFD should be used.
690             }
691 60 100 100     252 if (@decH == 3 && exists $max->{$decH[1]}) {
692 2         10 my $contract = join(CODE_SEP, @decH[1,2]);
693 2 50       12 exists $map->{$contract} and @decH = ($decH[0], $contract);
694             }
695             }
696              
697             @ce = map({
698 105 100 66     215 exists $map->{$_} ? @{ $map->{$_} } :
  257 100       947  
  121         380  
699             $uXS && _exists_simple($_) ? _fetch_simple($_) : ### XS only
700             $der->($_);
701             } @decH);
702             }
703             } elsif ($out && 0x10FFFF < $u) {
704 72         179 @ce = map _pack_override($_, $u, $der), $out->($u);
705             } else {
706 22777         36649 my $cjk = $self->{overrideCJK};
707 22777         31769 my $vers = $self->{UCA_Version};
708 22777 100 100     79723 if ($cjk && _isUIdeo($u, $vers)) {
    100 100        
      100        
709 11755         32378 @ce = map _pack_override($_, $u, $der), $cjk->($u);
710             } elsif ($vers == 8 && defined $cjk && _isUIdeo($u, 0)) {
711 96         321 @ce = _uideoCE_8($u);
712             } else {
713 10926         37246 @ce = $der->($u);
714             }
715             }
716 72329         388856 return map $self->varCE($_), @ce;
717             }
718              
719              
720             ##
721             ## string sortkey = getSortKey(string arg)
722             ##
723             sub getSortKey
724             {
725 59153     59153 1 95762 my $self = shift;
726 59153         91594 my $orig = shift;
727 59153         115649 my $str = $self->process($orig);
728 59153         110284 my $rEnt = $self->splitEnt($str); # get an arrayref of JCPS
729 59153         97686 my $vers = $self->{UCA_Version};
730 59153         84204 my $term = $self->{hangul_terminator};
731 59153         81368 my $lev = $self->{level};
732 59153         78449 my $iden = $self->{identical};
733              
734 59153         77132 my @buf; # weight arrays
735 59153 100       95780 if ($term) {
736 100         138 my $preHST = '';
737 100         472 my $termCE = $self->varCE(pack(VCE_TEMPLATE, NON_VAR, $term, 0,0,0));
738 100         237 foreach my $jcps (@$rEnt) {
739             # weird things like VL, TL-contraction are not considered!
740 243         1087 my $curHST = join '', map getHST($_, $vers), split /;/, $jcps;
741 243 100 100     2008 if ($preHST && !$curHST || # hangul before non-hangul
      66        
      66        
      100        
      66        
      66        
      66        
742             $preHST =~ /L\z/ && $curHST =~ /^T/ ||
743             $preHST =~ /V\z/ && $curHST =~ /^L/ ||
744             $preHST =~ /T\z/ && $curHST =~ /^[LV]/) {
745 25         46 push @buf, $termCE;
746             }
747 243         397 $preHST = $curHST;
748 243         467 push @buf, $self->getWt($jcps);
749             }
750 100 100       246 push @buf, $termCE if $preHST; # end at hangul
751             } else {
752 59053         105008 foreach my $jcps (@$rEnt) {
753 70090         132562 push @buf, $self->getWt($jcps);
754             }
755             }
756              
757 59153         250180 my $rkey = $self->mk_SortKey(\@buf); ### XS only
758              
759 59153 100 100     237979 if ($iden || $vers >= 26 && $lev == MaxLevel) {
      100        
760 8726         17762 $rkey .= LEVEL_SEP;
761 8726 100       15105 $rkey .= pack(TIE_TEMPLATE, unpack_U($str)) if $iden;
762             }
763 59153         249409 return $rkey;
764             }
765              
766              
767             ##
768             ## int compare = cmp(string a, string b)
769             ##
770 7505     7505 1 131413 sub cmp { $_[0]->getSortKey($_[1]) cmp $_[0]->getSortKey($_[2]) }
771 14478     14478 1 45388 sub eq { $_[0]->getSortKey($_[1]) eq $_[0]->getSortKey($_[2]) }
772 13     13 1 45 sub ne { $_[0]->getSortKey($_[1]) ne $_[0]->getSortKey($_[2]) }
773 5619     5619 1 32081 sub lt { $_[0]->getSortKey($_[1]) lt $_[0]->getSortKey($_[2]) }
774 6     6 1 26 sub le { $_[0]->getSortKey($_[1]) le $_[0]->getSortKey($_[2]) }
775 1659     1659 1 7423 sub gt { $_[0]->getSortKey($_[1]) gt $_[0]->getSortKey($_[2]) }
776 8     8 1 29 sub ge { $_[0]->getSortKey($_[1]) ge $_[0]->getSortKey($_[2]) }
777              
778             ##
779             ## list[strings] sorted = sort(list[strings] arg)
780             ##
781             sub sort {
782 26     26 1 985 my $obj = shift;
783             return
784 318         611 map { $_->[1] }
785 26         122 sort{ $a->[0] cmp $b->[0] }
  885         1303  
786             map [ $obj->getSortKey($_), $_ ], @_;
787             }
788              
789              
790             ##
791             ## bool _nonIgnorAtLevel(arrayref weights, int level)
792             ##
793             sub _nonIgnorAtLevel($$)
794             {
795 2216     2216   2978 my $wt = shift;
796 2216 50       3970 return if ! defined $wt;
797 2216         2787 my $lv = shift;
798 2216 100       6580 return grep($wt->[$_-1] != 0, MinLevel..$lv) ? TRUE : FALSE;
799             }
800              
801             ##
802             ## bool _eqArray(
803             ## arrayref of arrayref[weights] source,
804             ## arrayref of arrayref[weights] substr,
805             ## int level)
806             ## * comparison of graphemes vs graphemes.
807             ## @$source >= @$substr must be true (check it before call this);
808             ##
809             sub _eqArray($$$)
810             {
811 893     893   1311 my $source = shift;
812 893         1090 my $substr = shift;
813 893         1155 my $lev = shift;
814              
815 893         1905 for my $g (0..@$substr-1){
816             # Do the $g'th graphemes have the same number of AV weights?
817 1411 100       1855 return if @{ $source->[$g] } != @{ $substr->[$g] };
  1411         2058  
  1411         2654  
818              
819 1373         1771 for my $w (0..@{ $substr->[$g] }-1) {
  1373         2189  
820 1400         2243 for my $v (0..$lev-1) {
821 1994 100       5045 return if $source->[$g][$w][$v] != $substr->[$g][$w][$v];
822             }
823             }
824             }
825 144         386 return 1;
826             }
827              
828             ##
829             ## (int position, int length)
830             ## int position = index(string, substring, position, [undoc'ed global])
831             ##
832             ## With "global" (only for the list context),
833             ## returns list of arrayref[position, length].
834             ##
835             sub index
836             {
837 139     139 1 1539 my $self = shift;
838             $self->{preprocess} and
839 139 100       1440 croak "Don't use Preprocess with index(), match(), etc.";
840             $self->{normCode} and
841 127 100       557 croak "Don't use Normalization with index(), match(), etc.";
842              
843 123         207 my $str = shift;
844 123         291 my $len = length($str);
845 123         213 my $sub = shift;
846 123         311 my $subE = $self->splitEnt($sub);
847 123 100       257 my $pos = @_ ? shift : 0;
848 123 100       255 $pos = 0 if $pos < 0;
849 123         181 my $glob = shift;
850              
851 123         192 my $lev = $self->{level};
852             my $v2i = $self->{UCA_Version} >= 9 &&
853 123   100     497 $self->{variable} ne 'non-ignorable';
854              
855 123 100       296 if (! @$subE) {
856 6 0       14 my $temp = $pos <= 0 ? 0 : $len <= $pos ? $len : $pos;
    50          
857 6 100       39 return $glob
    100          
858             ? map([$_, 0], $temp..$len)
859             : wantarray ? ($temp,0) : $temp;
860             }
861 117 50       239 $len < $pos
    100          
862             and return wantarray ? () : NOMATCHPOS;
863 116 100       323 my $strE = $self->splitEnt($pos ? substr($str, $pos) : $str, TRUE);
864 116 50       247 @$strE
    100          
865             or return wantarray ? () : NOMATCHPOS;
866              
867 115         433 my(@strWt, @iniPos, @finPos, @subWt, @g_ret);
868              
869 115         0 my $last_is_variable;
870 115         369 for my $vwt (map $self->getWt($_), @$subE) {
871 416         1127 my($var, @wt) = unpack(VCE_TEMPLATE, $vwt);
872 416         861 my $to_be_pushed = _nonIgnorAtLevel(\@wt,$lev);
873              
874             # "Ignorable (L1, L2) after Variable" since track. v. 9
875 416 100       947 if ($v2i) {
876 384 100       785 if ($var) {
    100          
877 15         22 $last_is_variable = TRUE;
878             }
879             elsif (!$wt[0]) { # ignorable
880 52 50       95 $to_be_pushed = FALSE if $last_is_variable;
881             }
882             else {
883 317         481 $last_is_variable = FALSE;
884             }
885             }
886              
887 416 100 100     1597 if (@subWt && !$var && !$wt[0]) {
    100 100        
888 52 100       103 push @{ $subWt[-1] }, \@wt if $to_be_pushed;
  41         100  
889             } elsif ($to_be_pushed) {
890 351         827 push @subWt, [ \@wt ];
891             }
892             # else ===> skipped
893             }
894              
895 115         230 my $count = 0;
896 115         181 my $end = @$strE - 1;
897              
898 115         168 $last_is_variable = FALSE; # reuse
899 115         251 for (my $i = 0; $i <= $end; ) { # no $i++
900 1459         1989 my $found_base = 0;
901              
902             # fetch a grapheme
903 1459   100     3823 while ($i <= $end && $found_base == 0) {
904 1779         3494 for my $vwt ($self->getWt($strE->[$i][0])) {
905 1800         5012 my($var, @wt) = unpack(VCE_TEMPLATE, $vwt);
906 1800         3277 my $to_be_pushed = _nonIgnorAtLevel(\@wt,$lev);
907              
908             # "Ignorable (L1, L2) after Variable" since track. v. 9
909 1800 100       3486 if ($v2i) {
910 1733 100       3029 if ($var) {
    100          
911 316         488 $last_is_variable = TRUE;
912             }
913             elsif (!$wt[0]) { # ignorable
914 95 100       169 $to_be_pushed = FALSE if $last_is_variable;
915             }
916             else {
917 1322         1866 $last_is_variable = FALSE;
918             }
919             }
920              
921 1800 100 100     7306 if (@strWt && !$var && !$wt[0]) {
    100 100        
922 114 100       201 push @{ $strWt[-1] }, \@wt if $to_be_pushed;
  61         124  
923 114         251 $finPos[-1] = $strE->[$i][2];
924             } elsif ($to_be_pushed) {
925 1420         2923 push @strWt, [ \@wt ];
926 1420 100       2722 push @iniPos, $found_base ? NOMATCHPOS : $strE->[$i][1];
927 1420 100       2457 $finPos[-1] = NOMATCHPOS if $found_base;
928 1420         1959 push @finPos, $strE->[$i][2];
929 1420         2826 $found_base++;
930             }
931             # else ===> no-op
932             }
933 1779         5797 $i++;
934             }
935              
936             # try to match
937 1459   100     4560 while ( @strWt > @subWt || (@strWt == @subWt && $i > $end) ) {
      100        
938 897 100 100     3387 if ($iniPos[0] != NOMATCHPOS &&
      100        
939             $finPos[$#subWt] != NOMATCHPOS &&
940             _eqArray(\@strWt, \@subWt, $lev)) {
941 144         224 my $temp = $iniPos[0] + $pos;
942              
943 144 100       236 if ($glob) {
944 90         215 push @g_ret, [$temp, $finPos[$#subWt] - $iniPos[0]];
945 90         235 splice @strWt, 0, $#subWt;
946 90         146 splice @iniPos, 0, $#subWt;
947 90         147 splice @finPos, 0, $#subWt;
948             }
949             else {
950             return wantarray
951 54 100       448 ? ($temp, $finPos[$#subWt] - $iniPos[0])
952             : $temp;
953             }
954             }
955 843         1245 shift @strWt;
956 843         1303 shift @iniPos;
957 843         3673 shift @finPos;
958             }
959             }
960              
961 61 100       677 return $glob
    100          
962             ? @g_ret
963             : wantarray ? () : NOMATCHPOS;
964             }
965              
966             ##
967             ## scalarref to matching part = match(string, substring)
968             ##
969             sub match
970             {
971 35     35 1 1842 my $self = shift;
972 35 100       89 if (my($pos,$len) = $self->index($_[0], $_[1])) {
973 22         84 my $temp = substr($_[0], $pos, $len);
974 22 100       173 return wantarray ? $temp : \$temp;
975             # An lvalue ref \substr should be avoided,
976             # since its value is affected by modification of its referent.
977             }
978             else {
979 5         15 return;
980             }
981             }
982              
983             ##
984             ## arrayref matching parts = gmatch(string, substring)
985             ##
986             sub gmatch
987             {
988 6     6 1 331 my $self = shift;
989 6         11 my $str = shift;
990 6         9 my $sub = shift;
991 6         17 return map substr($str, $_->[0], $_->[1]),
992             $self->index($str, $sub, 0, 'g');
993             }
994              
995             ##
996             ## bool subst'ed = subst(string, substring, replace)
997             ##
998             sub subst
999             {
1000 5     5 1 203 my $self = shift;
1001 5 100       17 my $code = ref $_[2] eq 'CODE' ? $_[2] : FALSE;
1002              
1003 5 100       16 if (my($pos,$len) = $self->index($_[0], $_[1])) {
1004 3 100       22 if ($code) {
1005 1         5 my $mat = substr($_[0], $pos, $len);
1006 1         6 substr($_[0], $pos, $len, $code->($mat));
1007             } else {
1008 2         11 substr($_[0], $pos, $len, $_[2]);
1009             }
1010 3         19 return TRUE;
1011             }
1012             else {
1013 2         9 return FALSE;
1014             }
1015             }
1016              
1017             ##
1018             ## int count = gsubst(string, substring, replace)
1019             ##
1020             sub gsubst
1021             {
1022 34     34 1 986 my $self = shift;
1023 34 100       108 my $code = ref $_[2] eq 'CODE' ? $_[2] : FALSE;
1024 34         53 my $cnt = 0;
1025              
1026             # Replacement is carried out from the end, then use reverse.
1027 34         118 for my $pos_len (reverse $self->index($_[0], $_[1], 0, 'g')) {
1028 98 100       164 if ($code) {
1029 78         240 my $mat = substr($_[0], $pos_len->[0], $pos_len->[1]);
1030 78         208 substr($_[0], $pos_len->[0], $pos_len->[1], $code->($mat));
1031             } else {
1032 20         119 substr($_[0], $pos_len->[0], $pos_len->[1], $_[2]);
1033             }
1034 98         447 $cnt++;
1035             }
1036 34         115 return $cnt;
1037             }
1038              
1039             1;
1040             __END__