File Coverage

blib/lib/Unicode/Util.pm
Criterion Covered Total %
statement 128 137 93.4
branch 68 88 77.2
condition 9 15 60.0
subroutine 25 25 100.0
pod 6 13 46.1
total 236 278 84.8


line stmt bran cond sub pod time code
1             package Unicode::Util;
2              
3 11     11   558043 use 5.008;
  11         300  
  11         466  
4 11     11   68 use strict;
  11         20  
  11         424  
5 11     11   84 use warnings;
  11         33  
  11         348  
6 11     11   1178 use utf8;
  11         29  
  11         83  
7 11     11   10940 use parent 'Exporter';
  11         4132  
  11         58  
8              
9             our $VERSION = '0.10';
10             our @EXPORT_OK = qw(
11             grapheme_length
12             grapheme_chop
13             grapheme_reverse
14             grapheme_index
15             grapheme_rindex
16             grapheme_substr
17             grapheme_split
18             graph_length graph_chop graph_reverse
19             byte_length code_length code_chop
20             );
21             our %EXPORT_TAGS = (all => \@EXPORT_OK);
22              
23             sub grapheme_substr (\$$;$$);
24              
25             sub grapheme_length (;$) {
26 61     61 1 76825 my ($str) = @_;
27 61 100       523 $str = $_ unless @_;
28 61         844 return scalar( () = $str =~ m{ \X }xg );
29             }
30              
31             sub grapheme_chop (;\[$@%]) {
32 11     11 1 194 my ($ref) = @_;
33 11 100       36 $ref = \$_ unless @_;
34              
35 11 100       49 if (ref $ref eq 'SCALAR') {
    100          
    50          
36 6         108 $$ref =~ s{ ( \X ) \z }{}x;
37 6 100       35402 return defined $1 ? $1 : '';
38             }
39             elsif (ref $ref eq 'ARRAY') {
40 3 100       13 return undef unless @$ref;
41              
42 2         4 for my $i ( 0 .. $#{$ref} ) {
  2         9  
43 5 100       6 if ( $i < $#{$ref} ) {
  5         15  
44 3         27 $ref->[$i] =~ s{ \X \z }{}x;
45             }
46             else {
47 2         19 $ref->[$i] =~ s{ ( \X ) \z }{}x;
48 2 100       79 return defined $1 ? $1 : '';
49             }
50             }
51             }
52             elsif (ref $ref eq 'HASH') {
53 2         5 my $elems = keys %$ref;
54 2 100       10 return undef unless $elems;
55              
56 1         2 my $count = 0;
57 1         4 for my $str (values %$ref) {
58 2 100       7 if (++$count < $elems) {
59 1         11 $str =~ s{ \X \z }{}x;
60             }
61             else {
62 1         7 $str =~ s{ ( \X ) \z }{}x;
63 1 50       10 return defined $1 ? $1 : '';
64             }
65             }
66             }
67             }
68              
69             sub grapheme_reverse (;@) {
70 17     17 1 32715 my (@strings) = @_;
71 17 100       83 return reverse @strings if wantarray;
72 9 100       26 @strings = $_ unless @strings;
73 9         17 return join '', map { reverse m{ \X }xg } reverse @strings;
  14         233  
74             }
75              
76             sub grapheme_index ($$;$) {
77 26     26 1 81 my ($str, $substr, $pos) = @_;
78              
79 26 100 66     156 if (!looks_like_number($pos) || $pos < 0) {
    100          
80 18         28 $pos = 0;
81             }
82             elsif ($pos > (my $length = grapheme_length($str))) {
83 1         2 $pos = $length;
84             }
85              
86 26 100       957 return grapheme_length($1) + $[
87 11     11   20822 if $str =~ m{ ^ ( \X{$pos} \X*? ) \Q$substr\E }xg;
  11         5738  
  11         10523  
88              
89 4         121 return -1;
90             }
91              
92             sub grapheme_rindex ($$;$) {
93 26     26 1 70 my ($str, $substr, $pos) = @_;
94              
95 26 100 66     143 if (!looks_like_number($pos) || $pos < 0) {
96 18         24 $pos = 0;
97             }
98              
99 26 100       57 if ($pos) {
100 8 100       33 $str = grapheme_substr($str, 0, $pos + ($substr ? 1 : 0));
101             }
102              
103 26 100       606 return grapheme_length($1)
104             if $str =~ m{ ^ ( \X* ) \Q$substr\E }xg;
105              
106 4         18 return -1;
107             }
108              
109             sub grapheme_substr (\$$;$$) {
110 22     22 1 55 my ($str, $offset, $length, $replacement) = @_;
111              
112 22 100       91 if (@_ == 2) {
    100          
    50          
113 4 100       13 if ($offset >= 0) {
114 2 50       87 return $1 if $$str =~ m{ ^ \X{$offset} ( .* ) }x;
115             }
116             else {
117 2         25 my $abs_offset = abs $offset;
118 2 50       87 return $1 if $$str =~ m{ ( \X{0,$abs_offset} ) \z }x;
119             }
120             }
121             elsif (@_ == 3) {
122 13 100       30 if ($offset >= 0) {
123 12 100       26 if ($length >= 0) {
124 10 50       241 return $1 if $$str =~ m{
125             ^ \X{$offset}
126             ( \X{0,$length} )
127             }x;
128             }
129             else {
130 2         5 my $abs_length = abs $length;
131 2 50       90 return $1 if $$str =~ m{
132             ^ \X{$offset}
133             ( .*? )
134             \X{$abs_length} \z
135             }x;
136             }
137             }
138             else {
139 1         2 my $abs_offset = abs $offset;
140 1 50       3 if ($length >= 0) {
141 1 50       42 return $1 if $$str =~ m{
142             (?= \X{$abs_offset} \z )
143             ( \X{0,$length} )
144             }x;
145             }
146             else {
147 0         0 my $abs_length = abs $length;
148 0 0       0 return $1 if $$str =~ m{
149             (?= \X{$abs_offset} \z )
150             ( .*? )
151             ( \X{$abs_length} )
152             }x;
153             }
154             }
155             }
156             elsif (@_ == 4) {
157 5 100       13 if ($offset >= 0) {
158 4 50       11 if ($length >= 0) {
159 4         94 $$str =~ m{ ^ ( \X{$offset} ) }x;
160 4         15 my $codepoint_offset = length $1;
161 4 50       147 return $1 if $$str =~ s{
162             (?<= ^ .{$codepoint_offset} )
163             ( \X{0,$length} )
164             }{$replacement}x;
165             }
166             else {
167 0         0 $$str =~ m{ ^ ( \X{$offset} ) }x;
168 0         0 my $codepoint_offset = length $1;
169 0         0 my $abs_length = abs $length;
170 0 0       0 return $1 if $$str =~ s{
171             (?<= ^ .{$codepoint_offset} )
172             ( .*? )
173             (?= \X{$abs_length} \z )
174             }{$replacement}x;
175             }
176             }
177             else {
178 1         2 my $abs_offset = abs $offset;
179 1 50       4 if ($length >= 0) {
180 1 50       35 return $1 if $$str =~ s{
181             (?= \X{$abs_offset} \z )
182             ( \X{0,$length} )
183             }{$replacement}x;
184             }
185             else {
186 0         0 my $abs_length = abs $length;
187 0 0       0 return $1 if $$str =~ s{
188             (?= \X{$abs_offset} \z )
189             ( .*? )
190             (?= \X{$abs_length} )
191             }{$replacement}x;
192             }
193             }
194             }
195             }
196              
197             # experimental functions
198              
199             sub grapheme_split (;$$) {
200 9     9 0 68 my ($str) = @_;
201 9         88 my @graphs = $str =~ m{ \X }xg;
202 9         26863 return @graphs;
203             }
204              
205             # deprecated functions
206              
207 11     11   1295 use Encode qw( encode find_encoding );
  11         14077  
  11         1691  
208 11     11   13744 use Unicode::Normalize qw( normalize );
  11         44555  
  11         1265  
209 11     11   99 use Scalar::Util qw( looks_like_number );
  11         24  
  11         1643  
210              
211 11     11   185 use constant DEFAULT_ENCODING => 'UTF-8';
  11         19  
  11         1601  
212 11     11   58 use constant IS_NORMAL_FORM => qr{^ (?:NF)? K? [CD] $}xi;
  11         19  
  11         7242  
213              
214             $EXPORT_TAGS{length} = [qw( graph_length code_length byte_length )];
215              
216             sub graph_length {
217 2     2 0 465 my ($str) = @_;
218 2         9 utf8::upgrade($str);
219 2         70 return scalar( () = $str =~ m{ \X }xg );
220             }
221              
222             sub code_length {
223 2     2 0 37908 my ($str, $nf) = @_;
224 2         8 utf8::upgrade($str);
225              
226 2 50 33     9 if ($nf && $nf =~ IS_NORMAL_FORM) {
227 0         0 $str = normalize(uc $nf, $str);
228             }
229              
230 2         12 return length $str;
231             }
232              
233             sub byte_length {
234 12     12 0 6030 my ($str, $enc, $nf) = @_;
235 12         32 utf8::upgrade($str);
236              
237 12 100 66     76 if ( !$enc || !find_encoding($enc) ) {
238 3         6 $enc = DEFAULT_ENCODING;
239             }
240              
241 12 100 66     257 if ($nf && $nf =~ IS_NORMAL_FORM) {
242 4         21 $str = normalize(uc $nf, $str);
243             }
244              
245 12         131 return length encode($enc, $str);
246             }
247              
248             sub graph_chop {
249 1     1 0 8 my ($str) = @_;
250 1         3 utf8::upgrade($str);
251 1     10   42 $str =~ s{ \X \z }{}x;
  10         113  
  10         21  
  10         206  
252 1         25672 return $str;
253             }
254              
255             sub code_chop {
256 1     1 0 4 my ($str) = @_;
257 1         4 utf8::upgrade($str);
258 1         4 chop $str;
259 1         4 return $str;
260             }
261              
262             sub graph_reverse {
263 1     1 0 9 my ($str) = @_;
264 1         5 utf8::upgrade($str);
265 1         67 return join '', reverse $str =~ m{ \X }xg;
266             }
267              
268             1;
269              
270             __END__