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   37840 unless ("A" eq pack('U', 0x41)) {
5             die "Unicode::Collate cannot stringify a Unicode code point\n";
6             }
7 138 50       4090 unless (0x41 == unpack('U', 'A')) {
8 0         0 die "Unicode::Collate cannot get a Unicode code point\n";
9             }
10             }
11              
12 138     138   3395 use 5.006;
  138         504  
13 138     138   866 use strict;
  138         323  
  138         3936  
14 138     138   809 use warnings;
  138         325  
  138         4145  
15 138     138   817 use Carp;
  138         304  
  138         9565  
16 138     138   956 use File::Spec;
  138         302  
  138         4086  
17              
18 138     138   815 no warnings 'utf8';
  138         341  
  138         9407  
19              
20             our $VERSION = '1.29';
21             our $PACKAGE = __PACKAGE__;
22              
23             ### begin XS only ###
24 138     138   977 use XSLoader ();
  138         338  
  138         9288  
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   955 use constant TRUE => 1;
  138         300  
  138         16820  
33 138     138   969 use constant FALSE => "";
  138         328  
  138         8572  
34 138     138   938 use constant NOMATCHPOS => -1;
  138         341  
  138         8452  
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   879 use constant MinLevel => 1;
  138         287  
  138         7120  
43 138     138   808 use constant MaxLevel => 4;
  138         332  
  138         6759  
44              
45             # Minimum weights at level 2 and 3, respectively
46 138     138   852 use constant Min2Wt => 0x20;
  138         291  
  138         7158  
47 138     138   838 use constant Min3Wt => 0x02;
  138         268  
  138         8519  
48              
49             # Shifted weight at 4th level
50 138     138   948 use constant Shift4Wt => 0xFFFF;
  138         283  
  138         7244  
51              
52             # A boolean for Variable and 16-bit weights at 4 levels of Collation Element
53 138     138   933 use constant VCE_TEMPLATE => 'Cn4';
  138         333  
  138         7256  
54              
55             # A sort key: 16-bit weights
56 138     138   933 use constant KEY_TEMPLATE => 'n*';
  138         333  
  138         7225  
57              
58             # The tie-breaking: 32-bit weights
59 138     138   2826 use constant TIE_TEMPLATE => 'N*';
  138         474  
  138         9309  
60              
61             # Level separator in a sort key:
62             # i.e. pack(KEY_TEMPLATE, 0)
63 138     138   903 use constant LEVEL_SEP => "\0\0";
  138         333  
  138         7338  
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   872 use constant CODE_SEP => ';';
  138         301  
  138         7269  
74             # NOTE: in regex /;/ is used for $jcps!
75              
76             # boolean values of variable weights
77 138     138   870 use constant NON_VAR => 0; # Non-Variable character
  138         286  
  138         6814  
78 138     138   1517 use constant VAR => 1; # Variable character
  138         407  
  138         8128  
79              
80             # specific code points
81 138     138   891 use constant Hangul_SIni => 0xAC00;
  138         285  
  138         6917  
82 138     138   946 use constant Hangul_SFin => 0xD7A3;
  138         304  
  138         986681  
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 873 sub UCA_Version { '43' }
92              
93 10     10 1 39 sub Base_Unicode_Version { '13.0.0' }
94              
95             ######
96              
97             sub pack_U {
98 1706     1706 0 4622 return pack('U*', @_);
99             }
100              
101             sub unpack_U {
102 59438     59438 0 190215 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 100 my $self = shift;
136 8   100     46 return $self->{versionTable} || 'unknown';
137             }
138              
139             my (%ChangeOK, %ChangeNG);
140             @ChangeOK{ @ChangeOK } = ();
141             @ChangeNG{ @ChangeNG } = ();
142              
143             sub change {
144 823     823 1 19919 my $self = shift;
145 823         2761 my %hash = @_;
146 823         1369 my %old;
147 823 100       2372 if (exists $hash{alternate}) {
148 9 100       23 if (exists $hash{variable}) {
149 1         4 delete $hash{alternate};
150             } else {
151 8         16 $hash{variable} = $hash{alternate};
152             }
153             }
154 823         2745 foreach my $k (keys %hash) {
155 884 50       2254 if (exists $ChangeOK{$k}) {
    0          
156 884         2034 $old{$k} = $self->{$k};
157 884         1998 $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         2954 $self->checkCollator();
164 823 100       2885 return wantarray ? %old : $self;
165             }
166              
167             sub _checkLevel {
168 1059     1059   1693 my $level = shift;
169 1059         1721 my $key = shift; # 'level' or 'backwards'
170 1059 50       2579 MinLevel <= $level or croak sprintf
171             "Illegal level %d (in value for key '%s') lower than %d.",
172             $level, $key, MinLevel;
173 1059 50       3044 $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 1743 my $self = shift;
202 1043         3840 _checkLevel($self->{level}, 'level');
203              
204             $self->{derivCode} = $DerivCode{ $self->{UCA_Version} }
205 1043 50       3771 or croak "Illegal UCA version (passed $self->{UCA_Version}).";
206              
207             $self->{variable} ||= $self->{alternate} || $self->{variableTable} ||
208 1043   50     5031 $self->{alternateTable} || 'shifted';
      66        
209 1043         3063 $self->{variable} = $self->{alternate} = lc($self->{variable});
210             exists $VariableOK{ $self->{variable} }
211 1043 50       3002 or croak "$PACKAGE unknown variable parameter name: $self->{variable}";
212              
213 1043 100       2825 if (! defined $self->{backwards}) {
    100          
214 919         2066 $self->{backwardsFlag} = 0;
215             } elsif (! ref $self->{backwards}) {
216 11         55 _checkLevel($self->{backwards}, 'backwards');
217 11         35 $self->{backwardsFlag} = 1 << $self->{backwards};
218             } else {
219 113         158 my %level;
220 113         210 $self->{backwardsFlag} = 0;
221 113         152 for my $b (@{ $self->{backwards} }) {
  113         220  
222 5         13 _checkLevel($b, 'backwards');
223 5         18 $level{$b} = 1;
224             }
225 113         298 for my $v (sort keys %level) {
226 5         18 $self->{backwardsFlag} += 1 << $v;
227             }
228             }
229              
230 1043 100       2522 defined $self->{rearrange} or $self->{rearrange} = [];
231             ref $self->{rearrange}
232 1043 50       3138 or croak "$PACKAGE: list for rearrangement must be store in ARRAYREF";
233              
234             # keys of $self->{rearrangeHash} are $self->{rearrange}.
235 1043         1843 $self->{rearrangeHash} = undef;
236              
237 1043 100       1469 if (@{ $self->{rearrange} }) {
  1043         2687  
238 13         20 @{ $self->{rearrangeHash} }{ @{ $self->{rearrange} } } = ();
  13         111  
  13         28  
239             }
240              
241 1043         2009 $self->{normCode} = undef;
242              
243 1043 100       2409 if (defined $self->{normalization}) {
244 40         71 eval { require Unicode::Normalize };
  40         237  
245 40 50       136 $@ and croak "Unicode::Normalize is required to normalize strings";
246              
247 40   100     110 $CVgetCombinClass ||= \&Unicode::Normalize::getCombinClass;
248              
249 40 100       199 if ($self->{normalization} =~ /^(?:NF)D\z/) { # tweak for default
    100          
250 32         89 $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   318 Unicode::Normalize::normalize($norm, shift);
256 7         63 };
257 7         51 eval { $self->{normCode}->("") }; # try
  7         17  
258 7 50       83 $@ and croak "$PACKAGE unknown normalization form name: $norm";
259             }
260             }
261 1043         1778 return;
262             }
263              
264             sub new
265             {
266 220     220 0 14277 my $class = shift;
267 220         1260 my $self = bless { @_ }, $class;
268              
269             ### begin XS only ###
270 220 100 33     3388 if (! exists $self->{table} && !defined $self->{rewrite} &&
271             !defined $self->{undefName} && !defined $self->{ignoreName} &&
272             !defined $self->{undefChar} && !defined $self->{ignoreChar}) {
273 127         793 $self->{__useXS} = \&_fetch_simple;
274             } else {
275 93         277 $self->{__useXS} = undef;
276             }
277             ### end XS only ###
278              
279             # keys of $self->{suppressHash} are $self->{suppress}.
280 220 100 66     1012 if ($self->{suppress} && @{ $self->{suppress} }) {
  6         30  
281 6         17 @{ $self->{suppressHash} }{ @{ $self->{suppress} } } = ();
  6         304  
  6         16  
282             } # before read_table()
283              
284             # If undef is passed explicitly, no file is read.
285 220 100       872 $self->{table} = $KeyFile if ! exists $self->{table};
286 220 100       1429 $self->read_table() if defined $self->{table};
287              
288 220 100       1021 if ($self->{entry}) {
289 151         1407 while ($self->{entry} =~ /([^\n]+)/g) {
290 29683         56811 $self->parseEntry($1, TRUE);
291             }
292             }
293              
294             # only in new(), not in change()
295 220   100     1588 $self->{level} ||= MaxLevel;
296 220   66     1710 $self->{UCA_Version} ||= UCA_Version();
297              
298             $self->{overrideHangul} = FALSE
299 220 100       1158 if ! exists $self->{overrideHangul};
300             $self->{overrideCJK} = FALSE
301 220 100       942 if ! exists $self->{overrideCJK};
302             $self->{normalization} = 'NFD'
303 220 100       811 if ! exists $self->{normalization};
304             $self->{rearrange} = $self->{rearrangeTable} ||
305             ($self->{UCA_Version} <= 11 ? $DefaultRearrange : [])
306 220 100 33     2770 if ! exists $self->{rearrange};
307             $self->{backwards} = $self->{backwardsTable}
308 220 100       1124 if ! exists $self->{backwards};
309             exists $self->{long_contraction} or $self->{long_contraction}
310 220 50 100     1919 = 22 <= $self->{UCA_Version} && $self->{UCA_Version} <= 24;
311              
312             # checkCollator() will be called in change()
313 220         2158 $self->checkCollator();
314              
315 220         1943 return $self;
316             }
317              
318             sub parseAtmark {
319 684     684 0 3794 my $self = shift;
320 684         1233 my $line = shift; # after s/^\s*\@//
321              
322 684 100       4049 if ($line =~ /^version\s*(\S*)/) {
    50          
    50          
    50          
    50          
    50          
323 172   33     1831 $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 402 my $self = shift;
344              
345             ### begin XS only ###
346 172 100       698 if ($self->{__useXS}) {
347 127         30931 my @rest = _fetch_rest(); # complex matter need to parse
348 127         1128 for my $line (@rest) {
349 119888 50       241889 next if $line =~ /^\s*#/;
350              
351 119888 100       206933 if ($line =~ s/^\s*\@//) {
352 635         1878 $self->parseAtmark($line);
353             } else {
354 119253         204468 $self->parseEntry($line);
355             }
356             }
357 127         7202 return;
358             }
359             ### end XS only ###
360              
361 45         112 my($f, $fh);
362 45         142 foreach my $d (@INC) {
363 90         1567 $f = File::Spec->catfile($d, @Path, $self->{table});
364 90 100       3581 last if open($fh, $f);
365 45         212 $f = undef;
366             }
367 45 50       232 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         1789 while (my $line = <$fh>) {
373 71112 100       187340 next if $line =~ /^\s*#/;
374              
375 70303 100       139962 if ($line =~ s/^\s*\@//) {
376 49         214 $self->parseAtmark($line);
377             } else {
378 70254         125017 $self->parseEntry($line);
379             }
380             }
381 45         1044 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 294087 my $self = shift;
391 219190         336431 my $line = shift;
392 219190         264949 my $tailoring = shift;
393 219190         293925 my($name, $entry, @uv, @key);
394              
395 219190 100       386673 if (defined $self->{rewrite}) {
396 34764         63882 $line = $self->{rewrite}->($line);
397             }
398              
399 219190 100       680644 return if $line !~ /^\s*[0-9A-Fa-f]/;
400              
401             # removes comment and gets name
402 219089 100       667442 $name = $1
403             if $line =~ s/[#%]\s*(.*)//;
404 219089 100 100     446737 return if defined $self->{undefName} && $name =~ /$self->{undefName}/;
405              
406             # gets element
407 218665         560935 my($e, $k) = split /;/, $line;
408 218665 50       398740 croak "Wrong Entry: must be separated by ';' from "
409             if ! $k;
410              
411 218665         470907 @uv = _getHexArray($e);
412 218665 50       379427 return if !@uv;
413             return if @uv > 1 && $self->{suppressHash} && !$tailoring &&
414 218665 100 100     554463 exists $self->{suppressHash}{$uv[0]};
      100        
      100        
415 218655         465095 $entry = join(CODE_SEP, @uv); # in JCPS
416              
417 218655 100 100     627213 if (defined $self->{undefChar} || defined $self->{ignoreChar}) {
418 1690         2791 my $ele = pack_U(@uv);
419              
420             # regarded as if it were not stored in the table
421             return
422 1690 100 100     6291 if defined $self->{undefChar} && $ele =~ /$self->{undefChar}/;
423              
424             # replaced as completely ignorable
425             $k = '[.0000.0000.0000.0000]'
426 1686 100 100     6536 if defined $self->{ignoreChar} && $ele =~ /$self->{ignoreChar}/;
427             }
428              
429             # replaced as completely ignorable
430             $k = '[.0000.0000.0000.0000]'
431 218651 100 100     394417 if defined $self->{ignoreName} && $name =~ /$self->{ignoreName}/;
432              
433 218651         282883 my $is_L3_ignorable = TRUE;
434              
435 218651         948622 foreach my $arr ($k =~ /\[([^\[\]]+)\]/g) { # SPACEs allowed
436 355577         589760 my $var = $arr =~ /\*/; # exactly /^\*/ but be lenient.
437 355577         790693 my @wt = _getHexArray($arr);
438 355577         906318 push @key, pack(VCE_TEMPLATE, $var, @wt);
439 355577 100 100     873579 $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       688487 $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       417995 if (@uv > 1) {
452 127526 100 100     416719 if (!$self->{maxlength}{$uv[0]} || $self->{maxlength}{$uv[0]} < @uv) {
453 10875         24741 $self->{maxlength}{$uv[0]} = @uv;
454             }
455             }
456              
457             # contraction: be 1 or not exists (any false value is disallowed)
458 218651         752302 while (@uv > 2) {
459 2171         3167 pop @uv;
460 2171         4454 my $fake_entry = join(CODE_SEP, @uv); # in JCPS
461 2171         11318 $self->{contraction}{$fake_entry} = 1;
462             }
463             }
464              
465              
466             sub viewSortKey
467             {
468 258     258 1 529 my $self = shift;
469 258         386 my $str = shift;
470 258         615 $self->visualizeSortKey($self->getSortKey($str));
471             }
472              
473              
474             sub process
475             {
476 59153     59153 0 74535 my $self = shift;
477 59153         73169 my $str = shift;
478 59153         86633 my $prep = $self->{preprocess};
479 59153         77259 my $norm = $self->{normCode};
480              
481 59153 100       106718 $str = &$prep($str) if ref $prep;
482 59153 100       91679 $str = &$norm($str) if ref $norm;
483 59153         100416 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 76651 my $self = shift;
493 59392         74868 my $str = shift;
494 59392         72961 my $wLen = shift; # with Length
495              
496 59392         82559 my $map = $self->{mapping};
497 59392         77943 my $max = $self->{maxlength};
498 59392         76255 my $reH = $self->{rearrangeHash};
499 59392         81637 my $vers = $self->{UCA_Version};
500 59392   100     159034 my $ver9 = $vers >= 9 && $vers <= 11;
501 59392         84325 my $long = $self->{long_contraction};
502 59392         77971 my $uXS = $self->{__useXS}; ### XS only
503              
504 59392         74200 my @buf;
505              
506             # get array of Unicode code point of string.
507 59392         93462 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     119258 if ($reH && ! $wLen) {
513 132         288 for (my $i = 0; $i < @src; $i++) {
514 180 100 100     627 if (exists $reH->{ $src[$i] } && $i + 1 < @src) {
515 13         38 ($src[$i], $src[$i+1]) = ($src[$i+1], $src[$i]);
516 13         31 $i++;
517             }
518             }
519             }
520              
521             # remove a code point marked as a completely ignorable.
522 59392         122091 for (my $i = 0; $i < @src; $i++) {
523 84694 100 100     260119 if ($vers <= 20 && _isIllegal($src[$i])) {
    100          
524 75         178 $src[$i] = undef;
525             } elsif ($ver9) {
526             $src[$i] = undef if exists $map->{ $src[$i] }
527 1470 100 66     5233 ? @{ $map->{ $src[$i] } } == 0
  337 100       983  
528             : $uXS && _ignorable_simple($src[$i]); ### XS only
529             }
530             }
531              
532 59392         112930 for (my $i = 0; $i < @src; $i++) {
533 73218         104203 my $jcps = $src[$i];
534              
535             # skip removed code point
536 73218 100       121764 if (! defined $jcps) {
537 197 100 66     390 if ($wLen && @buf) {
538 15         25 $buf[-1][2] = $i + 1;
539             }
540 197         400 next;
541             }
542              
543 73021         90329 my $i_orig = $i;
544              
545             # find contraction
546 73021 100       142772 if (exists $max->{$jcps}) {
547 13226         18452 my $temp_jcps = $jcps;
548 13226         16781 my $jcpsLen = 1;
549 13226         19461 my $maxLen = $max->{$jcps};
550              
551 13226   100     43259 for (my $p = $i + 1; $jcpsLen < $maxLen && $p < @src; $p++) {
552 13298 100       24420 next if ! defined $src[$p];
553 13272         25537 $temp_jcps .= CODE_SEP . $src[$p];
554 13272         16883 $jcpsLen++;
555 13272 100       30740 if (exists $map->{$temp_jcps}) {
556 10756         15197 $jcps = $temp_jcps;
557 10756         24979 $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       25954 if ($self->{normalization}) {
570 165         253 my $cont = $self->{contraction};
571 165         212 my $preCC = 0;
572 165         215 my $preCC_uc = 0;
573 165         230 my $jcps_uc = $jcps;
574 165         575 my(@out, @out_uc);
575              
576 165         320 for (my $p = $i + 1; $p < @src; $p++) {
577 268 100       492 next if ! defined $src[$p];
578 252         441 my $curCC = $CVgetCombinClass->($src[$p]);
579 252 100       456 last unless $curCC;
580 219         346 my $tail = CODE_SEP . $src[$p];
581              
582 219 100 100     660 if ($preCC != $curCC && exists $map->{$jcps.$tail}) {
583 64         101 $jcps .= $tail;
584 64         112 push @out, $p;
585             } else {
586 155         208 $preCC = $curCC;
587             }
588              
589 219 100       446 next if !$long;
590              
591 119 100 100     443 if ($preCC_uc != $curCC &&
      66        
592             (exists $map->{$jcps_uc.$tail} ||
593             exists $cont->{$jcps_uc.$tail})) {
594 79         102 $jcps_uc .= $tail;
595 79         184 push @out_uc, $p;
596             } else {
597 40         89 $preCC_uc = $curCC;
598             }
599             }
600              
601 165 100 100     398 if (@out_uc && exists $map->{$jcps_uc}) {
602 39         62 $jcps = $jcps_uc;
603 39         109 $src[$_] = undef for @out_uc;
604             } else {
605 126         285 $src[$_] = undef for @out;
606             }
607             }
608             }
609              
610             # skip completely ignorable
611 73021 100 100     244126 if (exists $map->{$jcps} ? @{ $map->{$jcps} } == 0 :
  33130 100       67636  
612             $uXS && $jcps !~ /;/ && _ignorable_simple($jcps)) { ### XS only
613 334 100 100     865 if ($wLen && @buf) {
614 94         146 $buf[-1][2] = $i + 1;
615             }
616 334         733 next;
617             }
618              
619 72687 100       213944 push @buf, $wLen ? [$jcps, $i_orig, $i + 1] : $jcps;
620             }
621 59392         129402 return \@buf;
622             }
623              
624             ##
625             ## VCE = _pack_override(input, codepoint, derivCode)
626             ##
627             sub _pack_override ($$$) {
628 12423     12423   18026 my $r = shift;
629 12423         16106 my $u = shift;
630 12423         13539 my $der = shift;
631              
632 12423 100       22354 if (ref $r) {
    100          
633 1343         5101 return pack(VCE_TEMPLATE, NON_VAR, @$r);
634             } elsif (defined $r) {
635 8814         36615 return pack(VCE_TEMPLATE, NON_VAR, $r, Min2Wt, Min3Wt, $u);
636             } else {
637 2266 100       3454 $u = 0xFFFD if 0x10FFFF < $u;
638 2266         9127 return $der->($u);
639             }
640             }
641              
642             ##
643             ## list of VCE = getWt(JCPS)
644             ##
645             sub getWt
646             {
647 72483     72483 0 97297 my $self = shift;
648 72483         93403 my $u = shift;
649 72483         97410 my $map = $self->{mapping};
650 72483         91530 my $der = $self->{derivCode};
651 72483         89840 my $out = $self->{overrideOut};
652 72483         90347 my $uXS = $self->{__useXS}; ### XS only
653              
654 72483 50       119818 return if !defined $u;
655 72483 100 100     125024 return $self->varCE($HighestVCE) if $u eq 0xFFFF && $self->{highestFFFF};
656 72351 100 100     116646 return $self->varCE($minimalVCE) if $u eq 0xFFFE && $self->{minimalFFFE};
657 72329 100 100     237509 $u = 0xFFFD if $u !~ /;/ && 0x10FFFF < $u && !$out;
      100        
658              
659 72329         94811 my @ce;
660 72329 100 100     215025 if (exists $map->{$u}) {
    100 100        
    100 100        
    100          
661 32644         40396 @ce = @{ $map->{$u} }; # $u may be a contraction
  32644         70188  
662             ### begin XS only ###
663             } elsif ($uXS && _exists_simple($u)) {
664 16698         40890 @ce = _fetch_simple($u);
665             ### end XS only ###
666             } elsif (Hangul_SIni <= $u && $u <= Hangul_SFin) {
667 138         236 my $hang = $self->{overrideHangul};
668 138 100       305 if ($hang) {
    100          
669 23         55 @ce = map _pack_override($_, $u, $der), $hang->($u);
670             } elsif (!defined $hang) {
671 10         34 @ce = $der->($u);
672             } else {
673 105         184 my $max = $self->{maxlength};
674 105         304 my @decH = _decompHangul($u);
675              
676 105 100       225 if (@decH == 2) {
677 45         137 my $contract = join(CODE_SEP, @decH);
678 45 100       134 @decH = ($contract) if exists $map->{$contract};
679             } else { # must be <@decH == 3>
680 60 100       211 if (exists $max->{$decH[0]}) {
681 7         23 my $contract = join(CODE_SEP, @decH);
682 7 100       18 if (exists $map->{$contract}) {
683 1         4 @decH = ($contract);
684             } else {
685 6         20 $contract = join(CODE_SEP, @decH[0,1]);
686 6 100       38 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     241 if (@decH == 3 && exists $max->{$decH[1]}) {
692 2         13 my $contract = join(CODE_SEP, @decH[1,2]);
693 2 50       11 exists $map->{$contract} and @decH = ($decH[0], $contract);
694             }
695             }
696              
697             @ce = map({
698 105 100 66     207 exists $map->{$_} ? @{ $map->{$_} } :
  257 100       1001  
  121         308  
699             $uXS && _exists_simple($_) ? _fetch_simple($_) : ### XS only
700             $der->($_);
701             } @decH);
702             }
703             } elsif ($out && 0x10FFFF < $u) {
704 72         170 @ce = map _pack_override($_, $u, $der), $out->($u);
705             } else {
706 22777         36019 my $cjk = $self->{overrideCJK};
707 22777         30357 my $vers = $self->{UCA_Version};
708 22777 100 100     71462 if ($cjk && _isUIdeo($u, $vers)) {
    100 100        
      100        
709 11755         28241 @ce = map _pack_override($_, $u, $der), $cjk->($u);
710             } elsif ($vers == 8 && defined $cjk && _isUIdeo($u, 0)) {
711 96         312 @ce = _uideoCE_8($u);
712             } else {
713 10926         35640 @ce = $der->($u);
714             }
715             }
716 72329         365242 return map $self->varCE($_), @ce;
717             }
718              
719              
720             ##
721             ## string sortkey = getSortKey(string arg)
722             ##
723             sub getSortKey
724             {
725 59153     59153 1 87588 my $self = shift;
726 59153         88997 my $orig = shift;
727 59153         108663 my $str = $self->process($orig);
728 59153         106621 my $rEnt = $self->splitEnt($str); # get an arrayref of JCPS
729 59153         93053 my $vers = $self->{UCA_Version};
730 59153         77782 my $term = $self->{hangul_terminator};
731 59153         80053 my $lev = $self->{level};
732 59153         76392 my $iden = $self->{identical};
733              
734 59153         70911 my @buf; # weight arrays
735 59153 100       86979 if ($term) {
736 100         148 my $preHST = '';
737 100         458 my $termCE = $self->varCE(pack(VCE_TEMPLATE, NON_VAR, $term, 0,0,0));
738 100         232 foreach my $jcps (@$rEnt) {
739             # weird things like VL, TL-contraction are not considered!
740 243         1042 my $curHST = join '', map getHST($_, $vers), split /;/, $jcps;
741 243 100 100     2165 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         44 push @buf, $termCE;
746             }
747 243         403 $preHST = $curHST;
748 243         458 push @buf, $self->getWt($jcps);
749             }
750 100 100       260 push @buf, $termCE if $preHST; # end at hangul
751             } else {
752 59053         96412 foreach my $jcps (@$rEnt) {
753 70090         126287 push @buf, $self->getWt($jcps);
754             }
755             }
756              
757 59153         230449 my $rkey = $self->mk_SortKey(\@buf); ### XS only
758              
759 59153 100 100     223200 if ($iden || $vers >= 26 && $lev == MaxLevel) {
      100        
760 8726         17261 $rkey .= LEVEL_SEP;
761 8726 100       15148 $rkey .= pack(TIE_TEMPLATE, unpack_U($str)) if $iden;
762             }
763 59153         225714 return $rkey;
764             }
765              
766              
767             ##
768             ## int compare = cmp(string a, string b)
769             ##
770 7505     7505 1 116699 sub cmp { $_[0]->getSortKey($_[1]) cmp $_[0]->getSortKey($_[2]) }
771 14478     14478 1 40980 sub eq { $_[0]->getSortKey($_[1]) eq $_[0]->getSortKey($_[2]) }
772 13     13 1 46 sub ne { $_[0]->getSortKey($_[1]) ne $_[0]->getSortKey($_[2]) }
773 5619     5619 1 33547 sub lt { $_[0]->getSortKey($_[1]) lt $_[0]->getSortKey($_[2]) }
774 6     6 1 21 sub le { $_[0]->getSortKey($_[1]) le $_[0]->getSortKey($_[2]) }
775 1659     1659 1 7517 sub gt { $_[0]->getSortKey($_[1]) gt $_[0]->getSortKey($_[2]) }
776 8     8 1 25 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 860 my $obj = shift;
783             return
784 318         607 map { $_->[1] }
785 26         116 sort{ $a->[0] cmp $b->[0] }
  897         1267  
786             map [ $obj->getSortKey($_), $_ ], @_;
787             }
788              
789              
790             ##
791             ## bool _nonIgnorAtLevel(arrayref weights, int level)
792             ##
793             sub _nonIgnorAtLevel($$)
794             {
795 2216     2216   2968 my $wt = shift;
796 2216 50       3924 return if ! defined $wt;
797 2216         2759 my $lv = shift;
798 2216 100       6566 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   1331 my $source = shift;
812 893         1072 my $substr = shift;
813 893         1142 my $lev = shift;
814              
815 893         1719 for my $g (0..@$substr-1){
816             # Do the $g'th graphemes have the same number of AV weights?
817 1411 100       1671 return if @{ $source->[$g] } != @{ $substr->[$g] };
  1411         2112  
  1411         2601  
818              
819 1373         1846 for my $w (0..@{ $substr->[$g] }-1) {
  1373         2167  
820 1400         2101 for my $v (0..$lev-1) {
821 1994 100       5067 return if $source->[$g][$w][$v] != $substr->[$g][$w][$v];
822             }
823             }
824             }
825 144         385 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 1538 my $self = shift;
838             $self->{preprocess} and
839 139 100       1354 croak "Don't use Preprocess with index(), match(), etc.";
840             $self->{normCode} and
841 127 100       563 croak "Don't use Normalization with index(), match(), etc.";
842              
843 123         188 my $str = shift;
844 123         271 my $len = length($str);
845 123         204 my $sub = shift;
846 123         302 my $subE = $self->splitEnt($sub);
847 123 100       281 my $pos = @_ ? shift : 0;
848 123 100       261 $pos = 0 if $pos < 0;
849 123         185 my $glob = shift;
850              
851 123         197 my $lev = $self->{level};
852             my $v2i = $self->{UCA_Version} >= 9 &&
853 123   100     417 $self->{variable} ne 'non-ignorable';
854              
855 123 100       284 if (! @$subE) {
856 6 0       15 my $temp = $pos <= 0 ? 0 : $len <= $pos ? $len : $pos;
    50          
857 6 100       37 return $glob
    100          
858             ? map([$_, 0], $temp..$len)
859             : wantarray ? ($temp,0) : $temp;
860             }
861 117 50       269 $len < $pos
    100          
862             and return wantarray ? () : NOMATCHPOS;
863 116 100       303 my $strE = $self->splitEnt($pos ? substr($str, $pos) : $str, TRUE);
864 116 50       262 @$strE
    100          
865             or return wantarray ? () : NOMATCHPOS;
866              
867 115         274 my(@strWt, @iniPos, @finPos, @subWt, @g_ret);
868              
869 115         0 my $last_is_variable;
870 115         359 for my $vwt (map $self->getWt($_), @$subE) {
871 416         1138 my($var, @wt) = unpack(VCE_TEMPLATE, $vwt);
872 416         781 my $to_be_pushed = _nonIgnorAtLevel(\@wt,$lev);
873              
874             # "Ignorable (L1, L2) after Variable" since track. v. 9
875 416 100       871 if ($v2i) {
876 384 100       787 if ($var) {
    100          
877 15         21 $last_is_variable = TRUE;
878             }
879             elsif (!$wt[0]) { # ignorable
880 52 50       86 $to_be_pushed = FALSE if $last_is_variable;
881             }
882             else {
883 317         488 $last_is_variable = FALSE;
884             }
885             }
886              
887 416 100 100     1571 if (@subWt && !$var && !$wt[0]) {
    100 100        
888 52 100       100 push @{ $subWt[-1] }, \@wt if $to_be_pushed;
  41         103  
889             } elsif ($to_be_pushed) {
890 351         808 push @subWt, [ \@wt ];
891             }
892             # else ===> skipped
893             }
894              
895 115         228 my $count = 0;
896 115         216 my $end = @$strE - 1;
897              
898 115         165 $last_is_variable = FALSE; # reuse
899 115         248 for (my $i = 0; $i <= $end; ) { # no $i++
900 1459         1928 my $found_base = 0;
901              
902             # fetch a grapheme
903 1459   100     3811 while ($i <= $end && $found_base == 0) {
904 1779         3476 for my $vwt ($self->getWt($strE->[$i][0])) {
905 1800         5120 my($var, @wt) = unpack(VCE_TEMPLATE, $vwt);
906 1800         3437 my $to_be_pushed = _nonIgnorAtLevel(\@wt,$lev);
907              
908             # "Ignorable (L1, L2) after Variable" since track. v. 9
909 1800 100       3362 if ($v2i) {
910 1733 100       3054 if ($var) {
    100          
911 316         444 $last_is_variable = TRUE;
912             }
913             elsif (!$wt[0]) { # ignorable
914 95 100       176 $to_be_pushed = FALSE if $last_is_variable;
915             }
916             else {
917 1322         1896 $last_is_variable = FALSE;
918             }
919             }
920              
921 1800 100 100     7143 if (@strWt && !$var && !$wt[0]) {
    100 100        
922 114 100       231 push @{ $strWt[-1] }, \@wt if $to_be_pushed;
  61         125  
923 114         249 $finPos[-1] = $strE->[$i][2];
924             } elsif ($to_be_pushed) {
925 1420         2785 push @strWt, [ \@wt ];
926 1420 100       2761 push @iniPos, $found_base ? NOMATCHPOS : $strE->[$i][1];
927 1420 100       2454 $finPos[-1] = NOMATCHPOS if $found_base;
928 1420         1995 push @finPos, $strE->[$i][2];
929 1420         2578 $found_base++;
930             }
931             # else ===> no-op
932             }
933 1779         5795 $i++;
934             }
935              
936             # try to match
937 1459   100     4426 while ( @strWt > @subWt || (@strWt == @subWt && $i > $end) ) {
      100        
938 897 100 100     3428 if ($iniPos[0] != NOMATCHPOS &&
      100        
939             $finPos[$#subWt] != NOMATCHPOS &&
940             _eqArray(\@strWt, \@subWt, $lev)) {
941 144         252 my $temp = $iniPos[0] + $pos;
942              
943 144 100       253 if ($glob) {
944 90         202 push @g_ret, [$temp, $finPos[$#subWt] - $iniPos[0]];
945 90         231 splice @strWt, 0, $#subWt;
946 90         174 splice @iniPos, 0, $#subWt;
947 90         139 splice @finPos, 0, $#subWt;
948             }
949             else {
950             return wantarray
951 54 100       419 ? ($temp, $finPos[$#subWt] - $iniPos[0])
952             : $temp;
953             }
954             }
955 843         1310 shift @strWt;
956 843         1308 shift @iniPos;
957 843         3684 shift @finPos;
958             }
959             }
960              
961 61 100       675 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 1863 my $self = shift;
972 35 100       97 if (my($pos,$len) = $self->index($_[0], $_[1])) {
973 22         88 my $temp = substr($_[0], $pos, $len);
974 22 100       112 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         18 return;
980             }
981             }
982              
983             ##
984             ## arrayref matching parts = gmatch(string, substring)
985             ##
986             sub gmatch
987             {
988 6     6 1 301 my $self = shift;
989 6         11 my $str = shift;
990 6         9 my $sub = shift;
991 6         13 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 240 my $self = shift;
1001 5 100       17 my $code = ref $_[2] eq 'CODE' ? $_[2] : FALSE;
1002              
1003 5 100       14 if (my($pos,$len) = $self->index($_[0], $_[1])) {
1004 3 100       26 if ($code) {
1005 1         5 my $mat = substr($_[0], $pos, $len);
1006 1         7 substr($_[0], $pos, $len, $code->($mat));
1007             } else {
1008 2         11 substr($_[0], $pos, $len, $_[2]);
1009             }
1010 3         21 return TRUE;
1011             }
1012             else {
1013 2         8 return FALSE;
1014             }
1015             }
1016              
1017             ##
1018             ## int count = gsubst(string, substring, replace)
1019             ##
1020             sub gsubst
1021             {
1022 34     34 1 1012 my $self = shift;
1023 34 100       101 my $code = ref $_[2] eq 'CODE' ? $_[2] : FALSE;
1024 34         57 my $cnt = 0;
1025              
1026             # Replacement is carried out from the end, then use reverse.
1027 34         135 for my $pos_len (reverse $self->index($_[0], $_[1], 0, 'g')) {
1028 98 100       176 if ($code) {
1029 78         271 my $mat = substr($_[0], $pos_len->[0], $pos_len->[1]);
1030 78         209 substr($_[0], $pos_len->[0], $pos_len->[1], $code->($mat));
1031             } else {
1032 20         44 substr($_[0], $pos_len->[0], $pos_len->[1], $_[2]);
1033             }
1034 98         533 $cnt++;
1035             }
1036 34         90 return $cnt;
1037             }
1038              
1039             1;
1040             __END__