File Coverage

blib/lib/Unicode/Stringprep.pm
Criterion Covered Total %
statement 128 132 96.9
branch 52 56 92.8
condition 8 11 72.7
subroutine 26 26 100.0
pod 1 1 100.0
total 215 226 95.1


line stmt bran cond sub pod time code
1             package Unicode::Stringprep;
2              
3             require 5.008_003;
4              
5 11     11   436641 use strict;
  11         27  
  11         401  
6 11     11   3555 use utf8;
  11         47  
  11         80  
7 11     11   296 use warnings;
  11         34  
  11         1285  
8              
9             our $VERSION = "1.104";
10             $VERSION = eval $VERSION;
11              
12             require Exporter;
13             our @ISA = qw(Exporter);
14             our @EXPORT = qw(stringprep);
15              
16 11     11   84 use Carp;
  11         19  
  11         907  
17              
18 11     11   13660 use Unicode::Normalize();
  11         32326  
  11         407  
19              
20 11     11   8243 use Unicode::Stringprep::Unassigned;
  11         40  
  11         547  
21 11     11   12038 use Unicode::Stringprep::Mapping;
  11         51  
  11         745  
22 11     11   16727 use Unicode::Stringprep::Prohibited;
  11         67  
  11         435  
23 11     11   6547 use Unicode::Stringprep::BiDi;
  11         39  
  11         18531  
24              
25             sub new {
26 10     10 1 6670 my $self = shift;
27 10   33     102 my $class = ref($self) || $self;
28 10         95 return bless _compile(@_), $class;
29             }
30              
31             ## Here be eval dragons
32              
33             sub _compile {
34 10     10   27 my $unicode_version = shift;
35 10         22 my $mapping_tables = shift;
36 10         35 my $unicode_normalization = uc shift;
37 10         21 my $prohibited_tables = shift;
38 10         236 my $bidi_check = shift;
39 10         28 my $unassigned_check = shift;
40              
41 10 50       62 croak 'Unsupported Unicode version '.$unicode_version.'.'
42             if $unicode_version != 3.2;
43              
44 10         47 my $mapping_sub = _compile_mapping($mapping_tables);
45 10         1459 my $normalization_sub = _compile_normalization($unicode_normalization);
46 10         47 my $prohibited_sub = _compile_prohibited($prohibited_tables);
47 10 100       41 my $bidi_sub = $bidi_check ? '_check_bidi($string)' : undef;
48 10 100       45 my $unassigned_sub = $unassigned_check ? '_check_unassigned($string)' : undef;
49 10 100       39 my $pr29_sub = (defined $normalization_sub) ? '_check_pr29($string)' : undef;
50              
51 10         40 my $code = "sub { no warnings 'utf8';".
52             'my $string = shift;';
53              
54 10 50       55 $code .= '$string .= pack("U0");' if $] < 5.008;
55              
56 35 100       998 $code .= join('', map { $_ ? "{$_}\n" : ''}
  60         110  
57 10         32 grep { defined $_ }
58             $mapping_sub,
59             $normalization_sub,
60             $prohibited_sub,
61             $bidi_sub,
62             $unassigned_sub,
63             $pr29_sub ).
64             'return $string;'.
65             '}';
66              
67 10   50 10   1776 return eval $code || die $@;
  10         100  
  10         21  
  10         46586  
68             }
69              
70             ## generic compilation functions for matching/mapping characters
71             ##
72              
73             sub _compile_mapping {
74 132     132   66501 my %map = ();
75             sub _mapping_tables {
76 153     33   535 my $map = shift;
77 68         465 while(@_) {
78 4366         4402 my $data = shift;
79 4366 100       11279 if(ref($data) eq 'HASH') { %{$map} = (%{$map},%{$data}) }
  80 100       111  
  80 100       288  
  79         330  
  27         73  
80 48         128 elsif(ref($data) eq 'ARRAY') { _mapping_tables($map,@{$data}) }
  23         336  
81 4255         13979 elsif(defined $data){ $map->{$data} = shift };
82             }
83             }
84 132         238 _mapping_tables(\%map,@_);
85              
86 130 100       682 return '' if !%map;
87              
88             sub _compile_mapping_r {
89 1564     1564   1653 my $map = shift;
90 1564 100       2365 if($#_ <= 7) {
91 785         960 return (join '', (map { '$char == '.$_.
  4287         17082  
92 4287         7405 ' ? "'.(join '', map { quotemeta($_); } ( $$map{$_} )).'"'.
93             ' : ' } @_)).' die';
94             } else {
95 779         2760 my @a = splice @_, 0, int($#_/2);
96 779         1796 return '$char < '.$_[0].' ? ('.
97             _compile_mapping_r($map,@a).
98             ') : ('.
99             _compile_mapping_r($map,@_).
100             ')';
101             }
102             };
103              
104 101         985 my @from = sort { $a <=> $b } keys %map;
  39124         37383  
105              
106 126 100       651 return undef if !@from;
107              
108 126         401 return '$string =~ s/('._compile_set( map { $_ => $_ } @from).')/my $char = ord($1); '.
  4407         5527  
109             _compile_mapping_r(\%map, @from).'/ge;',
110             }
111              
112             sub _compile_set {
113 71     71   184 my @collect = ();
114             sub _set_tables {
115 122     122   220 my $set = shift;
116 122         377 while(@_) {
117 19613         22933 my $data = shift;
118 19613 100       72625 if(ref($data) eq 'HASH') { _set_tables($set, %{$data}); }
  0 100       0  
  0 100       0  
119 51         68 elsif(ref($data) eq 'ARRAY') { _set_tables($set, @{$data}); }
  51         168  
120 19561   100     20932 elsif(defined $data){ push @{$set}, [$data,shift || $data] };
  19561         100857  
121             }
122             }
123 71         853 _set_tables(\@collect,@_);
124              
125             # NB: This destroys @collect as it modifies the anonymous ARRAYs
126             # referenced in @collect.
127             # This is harmless as it only modifies ARRAYs after they've been
128             # inspected.
129              
130 71         160 my @set = ();
131 71         1452 foreach my $d (sort { $a->[0]<=>$b->[0] } @collect) {
  22092         37001  
132 19561 100 100     82859 if(!@set || $set[$#set]->[1]+1 < $d->[0]) {
    100          
133 15437         24195 push @set, $d;
134             } elsif($set[$#set]->[1] < $d->[1]) {
135 4074         6959 $set[$#set]->[1] = $d->[1];
136             }
137             }
138              
139 71 100       311 return undef if !@set;
140              
141 15437         54351 return '['.join('', map {
142 68         213 sprintf( $_->[0] >= $_->[1]
143             ? "\\x{%X}"
144             : "\\x{%X}-\\x{%X}",
145 15437 100       27779 @{$_})
146             } @set ).']';
147             }
148              
149             ## specific functions for individual stringprep steps
150             ##
151              
152             sub _compile_normalization {
153 10     10   29 my $unicode_normalization = uc shift;
154 10         35 $unicode_normalization =~ s/^NF//;
155              
156 10 100       56 return '$string = _NFKC_3_2($string)' if $unicode_normalization eq 'KC';
157 4 50       17 return undef if !$unicode_normalization;
158              
159 0         0 croak 'Unsupported Unicode normalization (NF)'.$unicode_normalization.'.';
160             }
161              
162             my $is_Unassigned = _compile_set(@Unicode::Stringprep::Unassigned::A1);
163              
164             sub _NFKC_3_2 {
165 136     136   286 my $string = shift;
166              
167             ## pre-map characters corrected in Corrigendum #4
168             ##
169 11     11   249 no warnings 'utf8';
  11         21  
  11         1013  
170 11     11   64 $string =~ tr/\x{2F868}\x{2F874}\x{2F91F}\x{2F95F}\x{2F9BF}/\x{2136A}\x{5F33}\x{43AB}\x{7AAE}\x{4D57}/;
  11         23  
  11         170  
  136         783  
171              
172             ## only normalize runs of assigned characters
173             ##
174 136         1766 my @s = split m/($is_Unassigned+)/o, $string;
175              
176 136         437 for( my $i = 0; $i <= $#s ; $i+=2 ) { # skips delimiters == is_Unassigned
177 11     11   92312 no warnings 'utf8';
  11         29  
  11         10368  
178 133         1457 $s[$i] = Unicode::Normalize::NFKC($s[$i]);
179             }
180 136         4234 return join '', @s;
181             }
182              
183             sub _check_unassigned {
184 26 100   26   942 if( shift =~ m/($is_Unassigned)/os ) {
185 1         17 die sprintf("unassigned character U+%04X",ord($1));
186             }
187             }
188              
189             sub _compile_prohibited {
190 10     10   46 my $prohibited = _compile_set(@_);
191              
192 10 100       88 if($prohibited) {
193             return
194 7         49 'if($string =~ m/('.$prohibited.')/os) {'.
195             'die sprintf("prohibited character U+%04X",ord($1))'.
196             '}';
197             }
198             }
199              
200             my $is_RandAL = _compile_set(@Unicode::Stringprep::BiDi::D1);
201             my $is_L = _compile_set(@Unicode::Stringprep::BiDi::D2);
202              
203             sub _check_bidi {
204 85     85   129 my $string = shift;
205              
206 85 100       2481 if($string =~ m/$is_RandAL/os) {
207 9 100       747 if($string =~ m/$is_L/os) {
    50          
    100          
208 4         37 die "string contains both RandALCat and LCat characters"
209             } elsif($string !~ m/^(?:$is_RandAL)/os) {
210 0         0 die "string contains RandALCat character but does not start with one"
211             } elsif($string !~ m/(?:$is_RandAL)$/os) {
212 3         31 die "string contains RandALCat character but does not end with one"
213             }
214             }
215             }
216              
217             my $is_Combining = _compile_set( 0x0300,0x0314, 0x0316,0x0319, 0x031C,0x0320,
218             0x0321,0x0322, 0x0323,0x0326, 0x0327,0x0328, 0x0329,0x0333, 0x0334,0x0338,
219             0x0339,0x033C, 0x033D,0x0344, 0x0347,0x0349, 0x034A,0x034C, 0x034D,0x034E,
220             0x0360,0x0361, 0x0363,0x036F, 0x0483,0x0486, 0x0592,0x0595, 0x0597,0x0599,
221             0x059C,0x05A1, 0x05A3,0x05A7, 0x05A8,0x05A9, 0x05AB,0x05AC, 0x0653,0x0654,
222             0x06D6,0x06DC, 0x06DF,0x06E2, 0x06E7,0x06E8, 0x06EB,0x06EC, 0x0732,0x0733,
223             0x0735,0x0736, 0x0737,0x0739, 0x073B,0x073C, 0x073F,0x0741, 0x0749,0x074A,
224             0x0953,0x0954, 0x0E38,0x0E39, 0x0E48,0x0E4B, 0x0EB8,0x0EB9, 0x0EC8,0x0ECB,
225             0x0F18,0x0F19, 0x0F7A,0x0F7D, 0x0F82,0x0F83, 0x0F86,0x0F87, 0x20D0,0x20D1,
226             0x20D2,0x20D3, 0x20D4,0x20D7, 0x20D8,0x20DA, 0x20DB,0x20DC, 0x20E5,0x20E6,
227             0x302E,0x302F, 0x3099,0x309A, 0xFE20,0xFE23,
228             0x1D165,0x1D166, 0x1D167,0x1D169, 0x1D16E,0x1D172, 0x1D17B,0x1D182,
229             0x1D185,0x1D189, 0x1D18A,0x1D18B, 0x1D1AA,0x1D1AD,
230             map { ($_,$_) } 0x0315, 0x031A, 0x031B, 0x0345, 0x0346, 0x0362, 0x0591,
231             0x0596, 0x059A, 0x059B, 0x05AA, 0x05AD, 0x05AE, 0x05AF, 0x05B0, 0x05B1,
232             0x05B2, 0x05B3, 0x05B4, 0x05B5, 0x05B6, 0x05B7, 0x05B8, 0x05B9, 0x05BB,
233             0x05BC, 0x05BD, 0x05BF, 0x05C1, 0x05C2, 0x05C4, 0x064B, 0x064C, 0x064D,
234             0x064E, 0x064F, 0x0650, 0x0651, 0x0652, 0x0655, 0x0670, 0x06E3, 0x06E4,
235             0x06EA, 0x06ED, 0x0711, 0x0730, 0x0731, 0x0734, 0x073A, 0x073D, 0x073E,
236             0x0742, 0x0743, 0x0744, 0x0745, 0x0746, 0x0747, 0x0748, 0x093C, 0x094D,
237             0x0951, 0x0952, 0x09BC, 0x09CD, 0x0A3C, 0x0A4D, 0x0ABC, 0x0ACD, 0x0B3C,
238             0x0B4D, 0x0BCD, 0x0C4D, 0x0C55, 0x0C56, 0x0CCD, 0x0D4D, 0x0DCA, 0x0E3A,
239             0x0F35, 0x0F37, 0x0F39, 0x0F71, 0x0F72, 0x0F74, 0x0F80, 0x0F84, 0x0FC6,
240             0x1037, 0x1039, 0x1714, 0x1734, 0x17D2, 0x18A9, 0x20E1, 0x20E7, 0x20E8,
241             0x20E9, 0x20EA, 0x302A, 0x302B, 0x302C, 0x302D, 0xFB1E, 0x1D16D, );
242              
243             my $is_HangulLV = _compile_set( map { ($_,$_) } 0xAC00, 0xAC1C, 0xAC38,
244             0xAC54, 0xAC70, 0xAC8C, 0xACA8, 0xACC4, 0xACE0, 0xACFC, 0xAD18, 0xAD34,
245             0xAD50, 0xAD6C, 0xAD88, 0xADA4, 0xADC0, 0xADDC, 0xADF8, 0xAE14, 0xAE30,
246             0xAE4C, 0xAE68, 0xAE84, 0xAEA0, 0xAEBC, 0xAED8, 0xAEF4, 0xAF10, 0xAF2C,
247             0xAF48, 0xAF64, 0xAF80, 0xAF9C, 0xAFB8, 0xAFD4, 0xAFF0, 0xB00C, 0xB028,
248             0xB044, 0xB060, 0xB07C, 0xB098, 0xB0B4, 0xB0D0, 0xB0EC, 0xB108, 0xB124,
249             0xB140, 0xB15C, 0xB178, 0xB194, 0xB1B0, 0xB1CC, 0xB1E8, 0xB204, 0xB220,
250             0xB23C, 0xB258, 0xB274, 0xB290, 0xB2AC, 0xB2C8, 0xB2E4, 0xB300, 0xB31C,
251             0xB338, 0xB354, 0xB370, 0xB38C, 0xB3A8, 0xB3C4, 0xB3E0, 0xB3FC, 0xB418,
252             0xB434, 0xB450, 0xB46C, 0xB488, 0xB4A4, 0xB4C0, 0xB4DC, 0xB4F8, 0xB514,
253             0xB530, 0xB54C, 0xB568, 0xB584, 0xB5A0, 0xB5BC, 0xB5D8, 0xB5F4, 0xB610,
254             0xB62C, 0xB648, 0xB664, 0xB680, 0xB69C, 0xB6B8, 0xB6D4, 0xB6F0, 0xB70C,
255             0xB728, 0xB744, 0xB760, 0xB77C, 0xB798, 0xB7B4, 0xB7D0, 0xB7EC, 0xB808,
256             0xB824, 0xB840, 0xB85C, 0xB878, 0xB894, 0xB8B0, 0xB8CC, 0xB8E8, 0xB904,
257             0xB920, 0xB93C, 0xB958, 0xB974, 0xB990, 0xB9AC, 0xB9C8, 0xB9E4, 0xBA00,
258             0xBA1C, 0xBA38, 0xBA54, 0xBA70, 0xBA8C, 0xBAA8, 0xBAC4, 0xBAE0, 0xBAFC,
259             0xBB18, 0xBB34, 0xBB50, 0xBB6C, 0xBB88, 0xBBA4, 0xBBC0, 0xBBDC, 0xBBF8,
260             0xBC14, 0xBC30, 0xBC4C, 0xBC68, 0xBC84, 0xBCA0, 0xBCBC, 0xBCD8, 0xBCF4,
261             0xBD10, 0xBD2C, 0xBD48, 0xBD64, 0xBD80, 0xBD9C, 0xBDB8, 0xBDD4, 0xBDF0,
262             0xBE0C, 0xBE28, 0xBE44, 0xBE60, 0xBE7C, 0xBE98, 0xBEB4, 0xBED0, 0xBEEC,
263             0xBF08, 0xBF24, 0xBF40, 0xBF5C, 0xBF78, 0xBF94, 0xBFB0, 0xBFCC, 0xBFE8,
264             0xC004, 0xC020, 0xC03C, 0xC058, 0xC074, 0xC090, 0xC0AC, 0xC0C8, 0xC0E4,
265             0xC100, 0xC11C, 0xC138, 0xC154, 0xC170, 0xC18C, 0xC1A8, 0xC1C4, 0xC1E0,
266             0xC1FC, 0xC218, 0xC234, 0xC250, 0xC26C, 0xC288, 0xC2A4, 0xC2C0, 0xC2DC,
267             0xC2F8, 0xC314, 0xC330, 0xC34C, 0xC368, 0xC384, 0xC3A0, 0xC3BC, 0xC3D8,
268             0xC3F4, 0xC410, 0xC42C, 0xC448, 0xC464, 0xC480, 0xC49C, 0xC4B8, 0xC4D4,
269             0xC4F0, 0xC50C, 0xC528, 0xC544, 0xC560, 0xC57C, 0xC598, 0xC5B4, 0xC5D0,
270             0xC5EC, 0xC608, 0xC624, 0xC640, 0xC65C, 0xC678, 0xC694, 0xC6B0, 0xC6CC,
271             0xC6E8, 0xC704, 0xC720, 0xC73C, 0xC758, 0xC774, 0xC790, 0xC7AC, 0xC7C8,
272             0xC7E4, 0xC800, 0xC81C, 0xC838, 0xC854, 0xC870, 0xC88C, 0xC8A8, 0xC8C4,
273             0xC8E0, 0xC8FC, 0xC918, 0xC934, 0xC950, 0xC96C, 0xC988, 0xC9A4, 0xC9C0,
274             0xC9DC, 0xC9F8, 0xCA14, 0xCA30, 0xCA4C, 0xCA68, 0xCA84, 0xCAA0, 0xCABC,
275             0xCAD8, 0xCAF4, 0xCB10, 0xCB2C, 0xCB48, 0xCB64, 0xCB80, 0xCB9C, 0xCBB8,
276             0xCBD4, 0xCBF0, 0xCC0C, 0xCC28, 0xCC44, 0xCC60, 0xCC7C, 0xCC98, 0xCCB4,
277             0xCCD0, 0xCCEC, 0xCD08, 0xCD24, 0xCD40, 0xCD5C, 0xCD78, 0xCD94, 0xCDB0,
278             0xCDCC, 0xCDE8, 0xCE04, 0xCE20, 0xCE3C, 0xCE58, 0xCE74, 0xCE90, 0xCEAC,
279             0xCEC8, 0xCEE4, 0xCF00, 0xCF1C, 0xCF38, 0xCF54, 0xCF70, 0xCF8C, 0xCFA8,
280             0xCFC4, 0xCFE0, 0xCFFC, 0xD018, 0xD034, 0xD050, 0xD06C, 0xD088, 0xD0A4,
281             0xD0C0, 0xD0DC, 0xD0F8, 0xD114, 0xD130, 0xD14C, 0xD168, 0xD184, 0xD1A0,
282             0xD1BC, 0xD1D8, 0xD1F4, 0xD210, 0xD22C, 0xD248, 0xD264, 0xD280, 0xD29C,
283             0xD2B8, 0xD2D4, 0xD2F0, 0xD30C, 0xD328, 0xD344, 0xD360, 0xD37C, 0xD398,
284             0xD3B4, 0xD3D0, 0xD3EC, 0xD408, 0xD424, 0xD440, 0xD45C, 0xD478, 0xD494,
285             0xD4B0, 0xD4CC, 0xD4E8, 0xD504, 0xD520, 0xD53C, 0xD558, 0xD574, 0xD590,
286             0xD5AC, 0xD5C8, 0xD5E4, 0xD600, 0xD61C, 0xD638, 0xD654, 0xD670, 0xD68C,
287             0xD6A8, 0xD6C4, 0xD6E0, 0xD6FC, 0xD718, 0xD734, 0xD750, 0xD76C, 0xD788, );
288              
289             sub _check_pr29 {
290 93 100   93   7562 die "String contains Unicode Corrigendum #5 problem sequences" if shift =~ m/
291             \x{09C7}$is_Combining+[\x{09BE}\x{09D7}] | # BENGALI VOWEL SIGN E
292             \x{0B47}$is_Combining+[\x{0B3E}\x{0B56}\x{0B57}] | # ORIYA VOWEL SIGN E
293             \x{0BC6}$is_Combining+[\x{0BBE}\x{0BD7}] | # TAMIL VOWEL SIGN E
294             \x{0BC7}$is_Combining+\x{0BBE} | # TAMIL VOWEL SIGN EE
295             \x{0B92}$is_Combining+\x{0BD7} | # TAMIL LETTER O
296             \x{0CC6}$is_Combining+[\x{0CC2}\x{0CD5}\x{0CD6}] | # KANNADA VOWEL SIGN E
297             [\x{0CBF}\x{0CCA}]$is_Combining\x{0CD5} | # KANNADA VOWEL SIGN I or KANNADA VOWEL SIGN O
298             \x{0D47}$is_Combining+\x{0D3E} | # MALAYALAM VOWEL SIGN EE
299             \x{0D46}$is_Combining+[\x{0D3E}\x{0D57}] | # MALAYALAM VOWEL SIGN E
300             \x{1025}$is_Combining+\x{102E} | # MYANMAR LETTER U
301             \x{0DD9}$is_Combining+[\x{0DCF}\x{0DDF}] | # SINHALA VOWEL SIGN KOMBUVA
302             [\x{1100}-\x{1112}]$is_Combining[\x{1161}-\x{1175} ] | # HANGUL CHOSEONG KIYEOK..HIEUH
303             ($is_HangulLV|[\x{1100}-\x{1112}][\x{1161}-\x{1175}])($is_Combining)([\x{11A8}-\x{11C2}]) # HANGUL SyllableType=LV
304             /osx;
305             }
306              
307             1;
308             __END__