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 137     137   38192 unless ("A" eq pack('U', 0x41)) {
5             die "Unicode::Collate cannot stringify a Unicode code point\n";
6             }
7 137 50       3872 unless (0x41 == unpack('U', 'A')) {
8 0         0 die "Unicode::Collate cannot get a Unicode code point\n";
9             }
10             }
11              
12 137     137   3067 use 5.006;
  137         464  
13 137     137   869 use strict;
  137         281  
  137         3879  
14 137     137   764 use warnings;
  137         287  
  137         4017  
15 137     137   777 use Carp;
  137         269  
  137         9031  
16 137     137   919 use File::Spec;
  137         345  
  137         4017  
17              
18 137     137   710 no warnings 'utf8';
  137         307  
  137         9014  
19              
20             our $VERSION = '1.28';
21             our $PACKAGE = __PACKAGE__;
22              
23             ### begin XS only ###
24 137     137   927 use XSLoader ();
  137         309  
  137         8996  
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 137     137   909 use constant TRUE => 1;
  137         303  
  137         16253  
33 137     137   897 use constant FALSE => "";
  137         292  
  137         7563  
34 137     137   903 use constant NOMATCHPOS => -1;
  137         398  
  137         8224  
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 137     137   840 use constant MinLevel => 1;
  137         270  
  137         6913  
43 137     137   813 use constant MaxLevel => 4;
  137         289  
  137         6565  
44              
45             # Minimum weights at level 2 and 3, respectively
46 137     137   833 use constant Min2Wt => 0x20;
  137         292  
  137         6787  
47 137     137   837 use constant Min3Wt => 0x02;
  137         263  
  137         8146  
48              
49             # Shifted weight at 4th level
50 137     137   840 use constant Shift4Wt => 0xFFFF;
  137         295  
  137         6929  
51              
52             # A boolean for Variable and 16-bit weights at 4 levels of Collation Element
53 137     137   811 use constant VCE_TEMPLATE => 'Cn4';
  137         344  
  137         6993  
54              
55             # A sort key: 16-bit weights
56 137     137   879 use constant KEY_TEMPLATE => 'n*';
  137         669  
  137         6843  
57              
58             # The tie-breaking: 32-bit weights
59 137     137   2627 use constant TIE_TEMPLATE => 'N*';
  137         402  
  137         8987  
60              
61             # Level separator in a sort key:
62             # i.e. pack(KEY_TEMPLATE, 0)
63 137     137   873 use constant LEVEL_SEP => "\0\0";
  137         344  
  137         7020  
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 137     137   852 use constant CODE_SEP => ';';
  137         331  
  137         7050  
74             # NOTE: in regex /;/ is used for $jcps!
75              
76             # boolean values of variable weights
77 137     137   828 use constant NON_VAR => 0; # Non-Variable character
  137         293  
  137         6630  
78 137     137   1373 use constant VAR => 1; # Variable character
  137         321  
  137         7624  
79              
80             # specific code points
81 137     137   857 use constant Hangul_SIni => 0xAC00;
  137         292  
  137         6559  
82 137     137   809 use constant Hangul_SFin => 0xD7A3;
  137         316  
  137         960138  
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 893 sub UCA_Version { '41' }
92              
93 10     10 1 36 sub Base_Unicode_Version { '12.1.0' }
94              
95             ######
96              
97             sub pack_U {
98 1706     1706 0 5017 return pack('U*', @_);
99             }
100              
101             sub unpack_U {
102 57734     57734 0 189024 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 77 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 791     791 1 19964 my $self = shift;
145 791         2554 my %hash = @_;
146 791         1338 my %old;
147 791 100       2231 if (exists $hash{alternate}) {
148 9 100       23 if (exists $hash{variable}) {
149 1         3 delete $hash{alternate};
150             } else {
151 8         15 $hash{variable} = $hash{alternate};
152             }
153             }
154 791         2636 foreach my $k (keys %hash) {
155 850 50       2127 if (exists $ChangeOK{$k}) {
    0          
156 850         1863 $old{$k} = $self->{$k};
157 850         1903 $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 791         2393 $self->checkCollator();
164 791 100       2681 return wantarray ? %old : $self;
165             }
166              
167             sub _checkLevel {
168 1026     1026   1644 my $level = shift;
169 1026         1636 my $key = shift; # 'level' or 'backwards'
170 1026 50       2540 MinLevel <= $level or croak sprintf
171             "Illegal level %d (in value for key '%s') lower than %d.",
172             $level, $key, MinLevel;
173 1026 50       2930 $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             );
198              
199             sub checkCollator {
200 1010     1010 0 1656 my $self = shift;
201 1010         3700 _checkLevel($self->{level}, 'level');
202              
203             $self->{derivCode} = $DerivCode{ $self->{UCA_Version} }
204 1010 50       3492 or croak "Illegal UCA version (passed $self->{UCA_Version}).";
205              
206             $self->{variable} ||= $self->{alternate} || $self->{variableTable} ||
207 1010   50     4662 $self->{alternateTable} || 'shifted';
      66        
208 1010         3069 $self->{variable} = $self->{alternate} = lc($self->{variable});
209             exists $VariableOK{ $self->{variable} }
210 1010 50       2834 or croak "$PACKAGE unknown variable parameter name: $self->{variable}";
211              
212 1010 100       2580 if (! defined $self->{backwards}) {
    100          
213 891         1945 $self->{backwardsFlag} = 0;
214             } elsif (! ref $self->{backwards}) {
215 11         50 _checkLevel($self->{backwards}, 'backwards');
216 11         35 $self->{backwardsFlag} = 1 << $self->{backwards};
217             } else {
218 108         142 my %level;
219 108         213 $self->{backwardsFlag} = 0;
220 108         146 for my $b (@{ $self->{backwards} }) {
  108         237  
221 5         13 _checkLevel($b, 'backwards');
222 5         28 $level{$b} = 1;
223             }
224 108         316 for my $v (sort keys %level) {
225 5         20 $self->{backwardsFlag} += 1 << $v;
226             }
227             }
228              
229 1010 100       2391 defined $self->{rearrange} or $self->{rearrange} = [];
230             ref $self->{rearrange}
231 1010 50       2953 or croak "$PACKAGE: list for rearrangement must be store in ARRAYREF";
232              
233             # keys of $self->{rearrangeHash} are $self->{rearrange}.
234 1010         1764 $self->{rearrangeHash} = undef;
235              
236 1010 100       1424 if (@{ $self->{rearrange} }) {
  1010         2590  
237 13         24 @{ $self->{rearrangeHash} }{ @{ $self->{rearrange} } } = ();
  13         103  
  13         35  
238             }
239              
240 1010         1819 $self->{normCode} = undef;
241              
242 1010 100       2283 if (defined $self->{normalization}) {
243 40         68 eval { require Unicode::Normalize };
  40         288  
244 40 50       102 $@ and croak "Unicode::Normalize is required to normalize strings";
245              
246 40   100     105 $CVgetCombinClass ||= \&Unicode::Normalize::getCombinClass;
247              
248 40 100       178 if ($self->{normalization} =~ /^(?:NF)D\z/) { # tweak for default
    100          
249 32         74 $self->{normCode} = \&Unicode::Normalize::NFD;
250             }
251             elsif ($self->{normalization} ne 'prenormalized') {
252 7         12 my $norm = $self->{normalization};
253             $self->{normCode} = sub {
254 147     147   331 Unicode::Normalize::normalize($norm, shift);
255 7         37 };
256 7         39 eval { $self->{normCode}->("") }; # try
  7         16  
257 7 50       80 $@ and croak "$PACKAGE unknown normalization form name: $norm";
258             }
259             }
260 1010         1692 return;
261             }
262              
263             sub new
264             {
265 219     219 0 15924 my $class = shift;
266 219         1170 my $self = bless { @_ }, $class;
267              
268             ### begin XS only ###
269 219 100 33     3279 if (! exists $self->{table} && !defined $self->{rewrite} &&
270             !defined $self->{undefName} && !defined $self->{ignoreName} &&
271             !defined $self->{undefChar} && !defined $self->{ignoreChar}) {
272 127         820 $self->{__useXS} = \&_fetch_simple;
273             } else {
274 92         248 $self->{__useXS} = undef;
275             }
276             ### end XS only ###
277              
278             # keys of $self->{suppressHash} are $self->{suppress}.
279 219 100 66     931 if ($self->{suppress} && @{ $self->{suppress} }) {
  6         33  
280 6         15 @{ $self->{suppressHash} }{ @{ $self->{suppress} } } = ();
  6         285  
  6         15  
281             } # before read_table()
282              
283             # If undef is passed explicitly, no file is read.
284 219 100       899 $self->{table} = $KeyFile if ! exists $self->{table};
285 219 100       1404 $self->read_table() if defined $self->{table};
286              
287 219 100       1021 if ($self->{entry}) {
288 151         1267 while ($self->{entry} =~ /([^\n]+)/g) {
289 29683         56899 $self->parseEntry($1, TRUE);
290             }
291             }
292              
293             # only in new(), not in change()
294 219   100     1533 $self->{level} ||= MaxLevel;
295 219   66     1558 $self->{UCA_Version} ||= UCA_Version();
296              
297             $self->{overrideHangul} = FALSE
298 219 100       1054 if ! exists $self->{overrideHangul};
299             $self->{overrideCJK} = FALSE
300 219 100       932 if ! exists $self->{overrideCJK};
301             $self->{normalization} = 'NFD'
302 219 100       836 if ! exists $self->{normalization};
303             $self->{rearrange} = $self->{rearrangeTable} ||
304             ($self->{UCA_Version} <= 11 ? $DefaultRearrange : [])
305 219 100 33     2789 if ! exists $self->{rearrange};
306             $self->{backwards} = $self->{backwardsTable}
307 219 100       1120 if ! exists $self->{backwards};
308             exists $self->{long_contraction} or $self->{long_contraction}
309 219 50 100     1740 = 22 <= $self->{UCA_Version} && $self->{UCA_Version} <= 24;
310              
311             # checkCollator() will be called in change()
312 219         2060 $self->checkCollator();
313              
314 219         1815 return $self;
315             }
316              
317             sub parseAtmark {
318 427     427 0 3362 my $self = shift;
319 427         883 my $line = shift; # after s/^\s*\@//
320              
321 427 100       3156 if ($line =~ /^version\s*(\S*)/) {
    50          
    50          
    50          
    50          
    50          
322 171   33     1855 $self->{versionTable} ||= $1;
323             }
324             elsif ($line =~ /^variable\s+(\S*)/) { # since UTS #10-9
325 0   0     0 $self->{variableTable} ||= $1;
326             }
327             elsif ($line =~ /^alternate\s+(\S*)/) { # till UTS #10-8
328 0   0     0 $self->{alternateTable} ||= $1;
329             }
330             elsif ($line =~ /^backwards\s+(\S*)/) {
331 0         0 push @{ $self->{backwardsTable} }, $1;
  0         0  
332             }
333             elsif ($line =~ /^forwards\s+(\S*)/) { # perhaps no use
334 0         0 push @{ $self->{forwardsTable} }, $1;
  0         0  
335             }
336             elsif ($line =~ /^rearrange\s+(.*)/) { # (\S*) is NG
337 0         0 push @{ $self->{rearrangeTable} }, _getHexArray($1);
  0         0  
338             }
339             }
340              
341             sub read_table {
342 171     171 0 406 my $self = shift;
343              
344             ### begin XS only ###
345 171 100       670 if ($self->{__useXS}) {
346 127         30409 my @rest = _fetch_rest(); # complex matter need to parse
347 127         1054 for my $line (@rest) {
348 119507 50       236576 next if $line =~ /^\s*#/;
349              
350 119507 100       201737 if ($line =~ s/^\s*\@//) {
351 381         1468 $self->parseAtmark($line);
352             } else {
353 119126         201896 $self->parseEntry($line);
354             }
355             }
356 127         6814 return;
357             }
358             ### end XS only ###
359              
360 44         148 my($f, $fh);
361 44         138 foreach my $d (@INC) {
362 88         1378 $f = File::Spec->catfile($d, @Path, $self->{table});
363 88 100       3405 last if open($fh, $f);
364 44         205 $f = undef;
365             }
366 44 50       220 if (!defined $f) {
367 0         0 $f = File::Spec->catfile(@Path, $self->{table});
368 0         0 croak("$PACKAGE: Can't locate $f in \@INC (\@INC contains: @INC)");
369             }
370              
371 44         1887 while (my $line = <$fh>) {
372 69774 100       191461 next if $line =~ /^\s*#/;
373              
374 68983 100       135338 if ($line =~ s/^\s*\@//) {
375 46         211 $self->parseAtmark($line);
376             } else {
377 68937         122557 $self->parseEntry($line);
378             }
379             }
380 44         889 close $fh;
381             }
382              
383              
384             ##
385             ## get $line, parse it, and write an entry in $self
386             ##
387             sub parseEntry
388             {
389 217746     217746 0 288421 my $self = shift;
390 217746         328889 my $line = shift;
391 217746         260938 my $tailoring = shift;
392 217746         290321 my($name, $entry, @uv, @key);
393              
394 217746 100       383269 if (defined $self->{rewrite}) {
395 34292         61574 $line = $self->{rewrite}->($line);
396             }
397              
398 217746 100       670350 return if $line !~ /^\s*[0-9A-Fa-f]/;
399              
400             # removes comment and gets name
401 217647 100       656304 $name = $1
402             if $line =~ s/[#%]\s*(.*)//;
403 217647 100 100     436255 return if defined $self->{undefName} && $name =~ /$self->{undefName}/;
404              
405             # gets element
406 217223         551719 my($e, $k) = split /;/, $line;
407 217223 50       396882 croak "Wrong Entry: must be separated by ';' from "
408             if ! $k;
409              
410 217223         462719 @uv = _getHexArray($e);
411 217223 50       369935 return if !@uv;
412             return if @uv > 1 && $self->{suppressHash} && !$tailoring &&
413 217223 100 100     543401 exists $self->{suppressHash}{$uv[0]};
      100        
      100        
414 217213         454103 $entry = join(CODE_SEP, @uv); # in JCPS
415              
416 217213 100 100     619186 if (defined $self->{undefChar} || defined $self->{ignoreChar}) {
417 1690         2719 my $ele = pack_U(@uv);
418              
419             # regarded as if it were not stored in the table
420             return
421 1690 100 100     6420 if defined $self->{undefChar} && $ele =~ /$self->{undefChar}/;
422              
423             # replaced as completely ignorable
424             $k = '[.0000.0000.0000.0000]'
425 1686 100 100     6347 if defined $self->{ignoreChar} && $ele =~ /$self->{ignoreChar}/;
426             }
427              
428             # replaced as completely ignorable
429             $k = '[.0000.0000.0000.0000]'
430 217209 100 100     384497 if defined $self->{ignoreName} && $name =~ /$self->{ignoreName}/;
431              
432 217209         279292 my $is_L3_ignorable = TRUE;
433              
434 217209         933347 foreach my $arr ($k =~ /\[([^\[\]]+)\]/g) { # SPACEs allowed
435 354003         578061 my $var = $arr =~ /\*/; # exactly /^\*/ but be lenient.
436 354003         776010 my @wt = _getHexArray($arr);
437 354003         891982 push @key, pack(VCE_TEMPLATE, $var, @wt);
438 354003 100 100     855401 $is_L3_ignorable = FALSE
      100        
439             if $wt[0] || $wt[1] || $wt[2];
440             # Conformance Test for 3.1.1 and 4.0.0 shows Level 3 ignorable
441             # is completely ignorable.
442             # For expansion, an entry $is_L3_ignorable
443             # if and only if "all" CEs are [.0000.0000.0000].
444             }
445              
446             # mapping: be an array ref or not exists (any false value is disallowed)
447 217209 100       686652 $self->{mapping}{$entry} = $is_L3_ignorable ? [] : \@key;
448              
449             # maxlength: be more than 1 or not exists (any false value is disallowed)
450 217209 100       412825 if (@uv > 1) {
451 127398 100 100     407731 if (!$self->{maxlength}{$uv[0]} || $self->{maxlength}{$uv[0]} < @uv) {
452 10747         23469 $self->{maxlength}{$uv[0]} = @uv;
453             }
454             }
455              
456             # contraction: be 1 or not exists (any false value is disallowed)
457 217209         734253 while (@uv > 2) {
458 2171         2982 pop @uv;
459 2171         4272 my $fake_entry = join(CODE_SEP, @uv); # in JCPS
460 2171         10586 $self->{contraction}{$fake_entry} = 1;
461             }
462             }
463              
464              
465             sub viewSortKey
466             {
467 246     246 1 565 my $self = shift;
468 246         367 my $str = shift;
469 246         575 $self->visualizeSortKey($self->getSortKey($str));
470             }
471              
472              
473             sub process
474             {
475 57449     57449 0 76343 my $self = shift;
476 57449         72951 my $str = shift;
477 57449         89341 my $prep = $self->{preprocess};
478 57449         79702 my $norm = $self->{normCode};
479              
480 57449 100       106209 $str = &$prep($str) if ref $prep;
481 57449 100       93605 $str = &$norm($str) if ref $norm;
482 57449         98983 return $str;
483             }
484              
485             ##
486             ## arrayref of JCPS = splitEnt(string to be collated)
487             ## arrayref of arrayref[JCPS, ini_pos, fin_pos] = splitEnt(string, TRUE)
488             ##
489             sub splitEnt
490             {
491 57688     57688 0 77449 my $self = shift;
492 57688         74846 my $str = shift;
493 57688         74445 my $wLen = shift; # with Length
494              
495 57688         84050 my $map = $self->{mapping};
496 57688         77341 my $max = $self->{maxlength};
497 57688         75410 my $reH = $self->{rearrangeHash};
498 57688         81856 my $vers = $self->{UCA_Version};
499 57688   100     158033 my $ver9 = $vers >= 9 && $vers <= 11;
500 57688         83358 my $long = $self->{long_contraction};
501 57688         78090 my $uXS = $self->{__useXS}; ### XS only
502              
503 57688         72626 my @buf;
504              
505             # get array of Unicode code point of string.
506 57688         93360 my @src = unpack_U($str);
507              
508             # rearrangement:
509             # Character positions are not kept if rearranged,
510             # then neglected if $wLen is true.
511 57688 100 100     118495 if ($reH && ! $wLen) {
512 132         285 for (my $i = 0; $i < @src; $i++) {
513 180 100 100     624 if (exists $reH->{ $src[$i] } && $i + 1 < @src) {
514 13         31 ($src[$i], $src[$i+1]) = ($src[$i+1], $src[$i]);
515 13         30 $i++;
516             }
517             }
518             }
519              
520             # remove a code point marked as a completely ignorable.
521 57688         122419 for (my $i = 0; $i < @src; $i++) {
522 82994 100 100     254605 if ($vers <= 20 && _isIllegal($src[$i])) {
    100          
523 75         175 $src[$i] = undef;
524             } elsif ($ver9) {
525             $src[$i] = undef if exists $map->{ $src[$i] }
526 1346 100 66     5055 ? @{ $map->{ $src[$i] } } == 0
  321 100       1000  
527             : $uXS && _ignorable_simple($src[$i]); ### XS only
528             }
529             }
530              
531 57688         108924 for (my $i = 0; $i < @src; $i++) {
532 71518         103016 my $jcps = $src[$i];
533              
534             # skip removed code point
535 71518 100       121203 if (! defined $jcps) {
536 197 100 66     383 if ($wLen && @buf) {
537 15         20 $buf[-1][2] = $i + 1;
538             }
539 197         407 next;
540             }
541              
542 71321         89532 my $i_orig = $i;
543              
544             # find contraction
545 71321 100       144051 if (exists $max->{$jcps}) {
546 13226         17305 my $temp_jcps = $jcps;
547 13226         16062 my $jcpsLen = 1;
548 13226         17709 my $maxLen = $max->{$jcps};
549              
550 13226   100     39946 for (my $p = $i + 1; $jcpsLen < $maxLen && $p < @src; $p++) {
551 13298 100       22409 next if ! defined $src[$p];
552 13272         23447 $temp_jcps .= CODE_SEP . $src[$p];
553 13272         15378 $jcpsLen++;
554 13272 100       28048 if (exists $map->{$temp_jcps}) {
555 10756         14007 $jcps = $temp_jcps;
556 10756         22766 $i = $p;
557             }
558             }
559              
560             # discontiguous contraction with Combining Char (cf. UTS#10, S2.1).
561             # This process requires Unicode::Normalize.
562             # If "normalization" is undef, here should be skipped *always*
563             # (in spite of bool value of $CVgetCombinClass),
564             # since canonical ordering cannot be expected.
565             # Blocked combining character should not be contracted.
566              
567             # $self->{normCode} is false in the case of "prenormalized".
568 13226 100       23706 if ($self->{normalization}) {
569 165         226 my $cont = $self->{contraction};
570 165         209 my $preCC = 0;
571 165         201 my $preCC_uc = 0;
572 165         238 my $jcps_uc = $jcps;
573 165         278 my(@out, @out_uc);
574              
575 165         359 for (my $p = $i + 1; $p < @src; $p++) {
576 268 100       484 next if ! defined $src[$p];
577 252         474 my $curCC = $CVgetCombinClass->($src[$p]);
578 252 100       423 last unless $curCC;
579 219         370 my $tail = CODE_SEP . $src[$p];
580              
581 219 100 100     653 if ($preCC != $curCC && exists $map->{$jcps.$tail}) {
582 64         109 $jcps .= $tail;
583 64         99 push @out, $p;
584             } else {
585 155         226 $preCC = $curCC;
586             }
587              
588 219 100       464 next if !$long;
589              
590 119 100 100     452 if ($preCC_uc != $curCC &&
      66        
591             (exists $map->{$jcps_uc.$tail} ||
592             exists $cont->{$jcps_uc.$tail})) {
593 79         103 $jcps_uc .= $tail;
594 79         180 push @out_uc, $p;
595             } else {
596 40         91 $preCC_uc = $curCC;
597             }
598             }
599              
600 165 100 100     394 if (@out_uc && exists $map->{$jcps_uc}) {
601 39         60 $jcps = $jcps_uc;
602 39         112 $src[$_] = undef for @out_uc;
603             } else {
604 126         305 $src[$_] = undef for @out;
605             }
606             }
607             }
608              
609             # skip completely ignorable
610 71321 100 100     262299 if (exists $map->{$jcps} ? @{ $map->{$jcps} } == 0 :
  32916 100       67565  
611             $uXS && $jcps !~ /;/ && _ignorable_simple($jcps)) { ### XS only
612 334 100 100     848 if ($wLen && @buf) {
613 94         155 $buf[-1][2] = $i + 1;
614             }
615 334         789 next;
616             }
617              
618 70987 100       214611 push @buf, $wLen ? [$jcps, $i_orig, $i + 1] : $jcps;
619             }
620 57688         127248 return \@buf;
621             }
622              
623             ##
624             ## VCE = _pack_override(input, codepoint, derivCode)
625             ##
626             sub _pack_override ($$$) {
627 12367     12367   20509 my $r = shift;
628 12367         17121 my $u = shift;
629 12367         16752 my $der = shift;
630              
631 12367 100       24259 if (ref $r) {
    100          
632 1292         5412 return pack(VCE_TEMPLATE, NON_VAR, @$r);
633             } elsif (defined $r) {
634 8809         41031 return pack(VCE_TEMPLATE, NON_VAR, $r, Min2Wt, Min3Wt, $u);
635             } else {
636 2266 100       4251 $u = 0xFFFD if 0x10FFFF < $u;
637 2266         11448 return $der->($u);
638             }
639             }
640              
641             ##
642             ## list of VCE = getWt(JCPS)
643             ##
644             sub getWt
645             {
646 70783     70783 0 93096 my $self = shift;
647 70783         93263 my $u = shift;
648 70783         96116 my $map = $self->{mapping};
649 70783         93326 my $der = $self->{derivCode};
650 70783         91409 my $out = $self->{overrideOut};
651 70783         91022 my $uXS = $self->{__useXS}; ### XS only
652              
653 70783 50       118430 return if !defined $u;
654 70783 100 100     126803 return $self->varCE($HighestVCE) if $u eq 0xFFFF && $self->{highestFFFF};
655 70651 100 100     113228 return $self->varCE($minimalVCE) if $u eq 0xFFFE && $self->{minimalFFFE};
656 70629 100 100     234795 $u = 0xFFFD if $u !~ /;/ && 0x10FFFF < $u && !$out;
      100        
657              
658 70629         91384 my @ce;
659 70629 100 100     216995 if (exists $map->{$u}) {
    100 100        
    100 100        
    100          
660 32430         40337 @ce = @{ $map->{$u} }; # $u may be a contraction
  32430         70832  
661             ### begin XS only ###
662             } elsif ($uXS && _exists_simple($u)) {
663 16698         42771 @ce = _fetch_simple($u);
664             ### end XS only ###
665             } elsif (Hangul_SIni <= $u && $u <= Hangul_SFin) {
666 138         222 my $hang = $self->{overrideHangul};
667 138 100       315 if ($hang) {
    100          
668 23         54 @ce = map _pack_override($_, $u, $der), $hang->($u);
669             } elsif (!defined $hang) {
670 10         31 @ce = $der->($u);
671             } else {
672 105         161 my $max = $self->{maxlength};
673 105         284 my @decH = _decompHangul($u);
674              
675 105 100       251 if (@decH == 2) {
676 45         139 my $contract = join(CODE_SEP, @decH);
677 45 100       127 @decH = ($contract) if exists $map->{$contract};
678             } else { # must be <@decH == 3>
679 60 100       180 if (exists $max->{$decH[0]}) {
680 7         35 my $contract = join(CODE_SEP, @decH);
681 7 100       27 if (exists $map->{$contract}) {
682 1         3 @decH = ($contract);
683             } else {
684 6         23 $contract = join(CODE_SEP, @decH[0,1]);
685 6 100       37 exists $map->{$contract} and @decH = ($contract, $decH[2]);
686             }
687             # even if V's ignorable, LT contraction is not supported.
688             # If such a situation were required, NFD should be used.
689             }
690 60 100 100     310 if (@decH == 3 && exists $max->{$decH[1]}) {
691 2         8 my $contract = join(CODE_SEP, @decH[1,2]);
692 2 50       22 exists $map->{$contract} and @decH = ($decH[0], $contract);
693             }
694             }
695              
696             @ce = map({
697 105 100 66     204 exists $map->{$_} ? @{ $map->{$_} } :
  257 100       935  
  121         297  
698             $uXS && _exists_simple($_) ? _fetch_simple($_) : ### XS only
699             $der->($_);
700             } @decH);
701             }
702             } elsif ($out && 0x10FFFF < $u) {
703 67         155 @ce = map _pack_override($_, $u, $der), $out->($u);
704             } else {
705 21296         33874 my $cjk = $self->{overrideCJK};
706 21296         31971 my $vers = $self->{UCA_Version};
707 21296 100 100     72433 if ($cjk && _isUIdeo($u, $vers)) {
    100 100        
      100        
708 11648         30676 @ce = map _pack_override($_, $u, $der), $cjk->($u);
709             } elsif ($vers == 8 && defined $cjk && _isUIdeo($u, 0)) {
710 86         285 @ce = _uideoCE_8($u);
711             } else {
712 9562         31162 @ce = $der->($u);
713             }
714             }
715 70629         362429 return map $self->varCE($_), @ce;
716             }
717              
718              
719             ##
720             ## string sortkey = getSortKey(string arg)
721             ##
722             sub getSortKey
723             {
724 57449     57449 1 87144 my $self = shift;
725 57449         88151 my $orig = shift;
726 57449         112536 my $str = $self->process($orig);
727 57449         100728 my $rEnt = $self->splitEnt($str); # get an arrayref of JCPS
728 57449         89604 my $vers = $self->{UCA_Version};
729 57449         79553 my $term = $self->{hangul_terminator};
730 57449         75960 my $lev = $self->{level};
731 57449         74563 my $iden = $self->{identical};
732              
733 57449         72675 my @buf; # weight arrays
734 57449 100       86393 if ($term) {
735 100         148 my $preHST = '';
736 100         470 my $termCE = $self->varCE(pack(VCE_TEMPLATE, NON_VAR, $term, 0,0,0));
737 100         226 foreach my $jcps (@$rEnt) {
738             # weird things like VL, TL-contraction are not considered!
739 243         1096 my $curHST = join '', map getHST($_, $vers), split /;/, $jcps;
740 243 100 100     1947 if ($preHST && !$curHST || # hangul before non-hangul
      66        
      66        
      100        
      66        
      66        
      66        
741             $preHST =~ /L\z/ && $curHST =~ /^T/ ||
742             $preHST =~ /V\z/ && $curHST =~ /^L/ ||
743             $preHST =~ /T\z/ && $curHST =~ /^[LV]/) {
744 25         40 push @buf, $termCE;
745             }
746 243         386 $preHST = $curHST;
747 243         450 push @buf, $self->getWt($jcps);
748             }
749 100 100       276 push @buf, $termCE if $preHST; # end at hangul
750             } else {
751 57349         95284 foreach my $jcps (@$rEnt) {
752 68390         124447 push @buf, $self->getWt($jcps);
753             }
754             }
755              
756 57449         232124 my $rkey = $self->mk_SortKey(\@buf); ### XS only
757              
758 57449 100 100     221726 if ($iden || $vers >= 26 && $lev == MaxLevel) {
      100        
759 7594         14190 $rkey .= LEVEL_SEP;
760 7594 100       12374 $rkey .= pack(TIE_TEMPLATE, unpack_U($str)) if $iden;
761             }
762 57449         231818 return $rkey;
763             }
764              
765              
766             ##
767             ## int compare = cmp(string a, string b)
768             ##
769 6744     6744 1 98397 sub cmp { $_[0]->getSortKey($_[1]) cmp $_[0]->getSortKey($_[2]) }
770 14464     14464 1 43364 sub eq { $_[0]->getSortKey($_[1]) eq $_[0]->getSortKey($_[2]) }
771 13     13 1 43 sub ne { $_[0]->getSortKey($_[1]) ne $_[0]->getSortKey($_[2]) }
772 5590     5590 1 31875 sub lt { $_[0]->getSortKey($_[1]) lt $_[0]->getSortKey($_[2]) }
773 6     6 1 20 sub le { $_[0]->getSortKey($_[1]) le $_[0]->getSortKey($_[2]) }
774 1617     1617 1 7573 sub gt { $_[0]->getSortKey($_[1]) gt $_[0]->getSortKey($_[2]) }
775 8     8 1 28 sub ge { $_[0]->getSortKey($_[1]) ge $_[0]->getSortKey($_[2]) }
776              
777             ##
778             ## list[strings] sorted = sort(list[strings] arg)
779             ##
780             sub sort {
781 26     26 1 881 my $obj = shift;
782             return
783 318         629 map { $_->[1] }
784 26         120 sort{ $a->[0] cmp $b->[0] }
  897         1267  
785             map [ $obj->getSortKey($_), $_ ], @_;
786             }
787              
788              
789             ##
790             ## bool _nonIgnorAtLevel(arrayref weights, int level)
791             ##
792             sub _nonIgnorAtLevel($$)
793             {
794 2216     2216   3028 my $wt = shift;
795 2216 50       3905 return if ! defined $wt;
796 2216         2767 my $lv = shift;
797 2216 100       6457 return grep($wt->[$_-1] != 0, MinLevel..$lv) ? TRUE : FALSE;
798             }
799              
800             ##
801             ## bool _eqArray(
802             ## arrayref of arrayref[weights] source,
803             ## arrayref of arrayref[weights] substr,
804             ## int level)
805             ## * comparison of graphemes vs graphemes.
806             ## @$source >= @$substr must be true (check it before call this);
807             ##
808             sub _eqArray($$$)
809             {
810 893     893   1335 my $source = shift;
811 893         1108 my $substr = shift;
812 893         1112 my $lev = shift;
813              
814 893         1822 for my $g (0..@$substr-1){
815             # Do the $g'th graphemes have the same number of AV weights?
816 1411 100       1702 return if @{ $source->[$g] } != @{ $substr->[$g] };
  1411         1993  
  1411         2549  
817              
818 1373         1835 for my $w (0..@{ $substr->[$g] }-1) {
  1373         2223  
819 1400         2146 for my $v (0..$lev-1) {
820 1994 100       5149 return if $source->[$g][$w][$v] != $substr->[$g][$w][$v];
821             }
822             }
823             }
824 144         391 return 1;
825             }
826              
827             ##
828             ## (int position, int length)
829             ## int position = index(string, substring, position, [undoc'ed global])
830             ##
831             ## With "global" (only for the list context),
832             ## returns list of arrayref[position, length].
833             ##
834             sub index
835             {
836 139     139 1 1411 my $self = shift;
837             $self->{preprocess} and
838 139 100       1368 croak "Don't use Preprocess with index(), match(), etc.";
839             $self->{normCode} and
840 127 100       545 croak "Don't use Normalization with index(), match(), etc.";
841              
842 123         211 my $str = shift;
843 123         261 my $len = length($str);
844 123         197 my $sub = shift;
845 123         305 my $subE = $self->splitEnt($sub);
846 123 100       269 my $pos = @_ ? shift : 0;
847 123 100       257 $pos = 0 if $pos < 0;
848 123         181 my $glob = shift;
849              
850 123         190 my $lev = $self->{level};
851             my $v2i = $self->{UCA_Version} >= 9 &&
852 123   100     422 $self->{variable} ne 'non-ignorable';
853              
854 123 100       285 if (! @$subE) {
855 6 0       14 my $temp = $pos <= 0 ? 0 : $len <= $pos ? $len : $pos;
    50          
856 6 100       36 return $glob
    100          
857             ? map([$_, 0], $temp..$len)
858             : wantarray ? ($temp,0) : $temp;
859             }
860 117 50       239 $len < $pos
    100          
861             and return wantarray ? () : NOMATCHPOS;
862 116 100       287 my $strE = $self->splitEnt($pos ? substr($str, $pos) : $str, TRUE);
863 116 50       247 @$strE
    100          
864             or return wantarray ? () : NOMATCHPOS;
865              
866 115         265 my(@strWt, @iniPos, @finPos, @subWt, @g_ret);
867              
868 115         0 my $last_is_variable;
869 115         372 for my $vwt (map $self->getWt($_), @$subE) {
870 416         1110 my($var, @wt) = unpack(VCE_TEMPLATE, $vwt);
871 416         810 my $to_be_pushed = _nonIgnorAtLevel(\@wt,$lev);
872              
873             # "Ignorable (L1, L2) after Variable" since track. v. 9
874 416 100       868 if ($v2i) {
875 384 100       779 if ($var) {
    100          
876 15         20 $last_is_variable = TRUE;
877             }
878             elsif (!$wt[0]) { # ignorable
879 52 50       90 $to_be_pushed = FALSE if $last_is_variable;
880             }
881             else {
882 317         451 $last_is_variable = FALSE;
883             }
884             }
885              
886 416 100 100     1925 if (@subWt && !$var && !$wt[0]) {
    100 100        
887 52 100       99 push @{ $subWt[-1] }, \@wt if $to_be_pushed;
  41         104  
888             } elsif ($to_be_pushed) {
889 351         803 push @subWt, [ \@wt ];
890             }
891             # else ===> skipped
892             }
893              
894 115         217 my $count = 0;
895 115         186 my $end = @$strE - 1;
896              
897 115         179 $last_is_variable = FALSE; # reuse
898 115         246 for (my $i = 0; $i <= $end; ) { # no $i++
899 1459         1959 my $found_base = 0;
900              
901             # fetch a grapheme
902 1459   100     3869 while ($i <= $end && $found_base == 0) {
903 1779         3439 for my $vwt ($self->getWt($strE->[$i][0])) {
904 1800         5058 my($var, @wt) = unpack(VCE_TEMPLATE, $vwt);
905 1800         3357 my $to_be_pushed = _nonIgnorAtLevel(\@wt,$lev);
906              
907             # "Ignorable (L1, L2) after Variable" since track. v. 9
908 1800 100       3469 if ($v2i) {
909 1733 100       3202 if ($var) {
    100          
910 316         449 $last_is_variable = TRUE;
911             }
912             elsif (!$wt[0]) { # ignorable
913 95 100       176 $to_be_pushed = FALSE if $last_is_variable;
914             }
915             else {
916 1322         1932 $last_is_variable = FALSE;
917             }
918             }
919              
920 1800 100 100     7177 if (@strWt && !$var && !$wt[0]) {
    100 100        
921 114 100       194 push @{ $strWt[-1] }, \@wt if $to_be_pushed;
  61         157  
922 114         247 $finPos[-1] = $strE->[$i][2];
923             } elsif ($to_be_pushed) {
924 1420         2674 push @strWt, [ \@wt ];
925 1420 100       2882 push @iniPos, $found_base ? NOMATCHPOS : $strE->[$i][1];
926 1420 100       2370 $finPos[-1] = NOMATCHPOS if $found_base;
927 1420         1917 push @finPos, $strE->[$i][2];
928 1420         2558 $found_base++;
929             }
930             # else ===> no-op
931             }
932 1779         5520 $i++;
933             }
934              
935             # try to match
936 1459   100     4541 while ( @strWt > @subWt || (@strWt == @subWt && $i > $end) ) {
      100        
937 897 100 100     3346 if ($iniPos[0] != NOMATCHPOS &&
      100        
938             $finPos[$#subWt] != NOMATCHPOS &&
939             _eqArray(\@strWt, \@subWt, $lev)) {
940 144         239 my $temp = $iniPos[0] + $pos;
941              
942 144 100       262 if ($glob) {
943 90         203 push @g_ret, [$temp, $finPos[$#subWt] - $iniPos[0]];
944 90         254 splice @strWt, 0, $#subWt;
945 90         158 splice @iniPos, 0, $#subWt;
946 90         143 splice @finPos, 0, $#subWt;
947             }
948             else {
949             return wantarray
950 54 100       400 ? ($temp, $finPos[$#subWt] - $iniPos[0])
951             : $temp;
952             }
953             }
954 843         1311 shift @strWt;
955 843         1266 shift @iniPos;
956 843         3649 shift @finPos;
957             }
958             }
959              
960 61 100       637 return $glob
    100          
961             ? @g_ret
962             : wantarray ? () : NOMATCHPOS;
963             }
964              
965             ##
966             ## scalarref to matching part = match(string, substring)
967             ##
968             sub match
969             {
970 35     35 1 1675 my $self = shift;
971 35 100       105 if (my($pos,$len) = $self->index($_[0], $_[1])) {
972 22         74 my $temp = substr($_[0], $pos, $len);
973 22 100       102 return wantarray ? $temp : \$temp;
974             # An lvalue ref \substr should be avoided,
975             # since its value is affected by modification of its referent.
976             }
977             else {
978 5         13 return;
979             }
980             }
981              
982             ##
983             ## arrayref matching parts = gmatch(string, substring)
984             ##
985             sub gmatch
986             {
987 6     6 1 299 my $self = shift;
988 6         13 my $str = shift;
989 6         8 my $sub = shift;
990 6         16 return map substr($str, $_->[0], $_->[1]),
991             $self->index($str, $sub, 0, 'g');
992             }
993              
994             ##
995             ## bool subst'ed = subst(string, substring, replace)
996             ##
997             sub subst
998             {
999 5     5 1 239 my $self = shift;
1000 5 100       18 my $code = ref $_[2] eq 'CODE' ? $_[2] : FALSE;
1001              
1002 5 100       15 if (my($pos,$len) = $self->index($_[0], $_[1])) {
1003 3 100       10 if ($code) {
1004 1         5 my $mat = substr($_[0], $pos, $len);
1005 1         6 substr($_[0], $pos, $len, $code->($mat));
1006             } else {
1007 2         41 substr($_[0], $pos, $len, $_[2]);
1008             }
1009 3         21 return TRUE;
1010             }
1011             else {
1012 2         8 return FALSE;
1013             }
1014             }
1015              
1016             ##
1017             ## int count = gsubst(string, substring, replace)
1018             ##
1019             sub gsubst
1020             {
1021 34     34 1 948 my $self = shift;
1022 34 100       109 my $code = ref $_[2] eq 'CODE' ? $_[2] : FALSE;
1023 34         59 my $cnt = 0;
1024              
1025             # Replacement is carried out from the end, then use reverse.
1026 34         111 for my $pos_len (reverse $self->index($_[0], $_[1], 0, 'g')) {
1027 98 100       161 if ($code) {
1028 78         271 my $mat = substr($_[0], $pos_len->[0], $pos_len->[1]);
1029 78         181 substr($_[0], $pos_len->[0], $pos_len->[1], $code->($mat));
1030             } else {
1031 20         37 substr($_[0], $pos_len->[0], $pos_len->[1], $_[2]);
1032             }
1033 98         444 $cnt++;
1034             }
1035 34         98 return $cnt;
1036             }
1037              
1038             1;
1039             __END__