File Coverage

blib/lib/Lingua/EO/Orthography.pm
Criterion Covered Total %
statement 84 84 100.0
branch 14 14 100.0
condition 3 3 100.0
subroutine 26 26 100.0
pod 7 7 100.0
total 134 134 100.0


line stmt bran cond sub pod time code
1             package Lingua::EO::Orthography;
2              
3              
4             # ****************************************************************
5             # perl dependency
6             # ****************************************************************
7              
8 1     1   78041 use 5.008_001;
  1         4  
  1         40  
9              
10              
11             # ****************************************************************
12             # pragma(s)
13             # ****************************************************************
14              
15 1     1   6 use strict;
  1         2  
  1         35  
16 1     1   6 use warnings;
  1         7  
  1         30  
17 1     1   5 use utf8;
  1         2  
  1         8  
18              
19              
20             # ****************************************************************
21             # general depencency(-ies)
22             # ****************************************************************
23              
24 1     1   29 use Carp qw(confess);
  1         2  
  1         148  
25 1     1   821 use Data::Util qw(:check neat);
  1         2291  
  1         270  
26 1     1   1072 use List::MoreUtils qw(any apply uniq);
  1         1925  
  1         110  
27 1     1   1156 use Memoize qw(memoize);
  1         3581  
  1         77  
28 1     1   8775 use Regexp::Assemble;
  1         23041  
  1         44  
29 1     1   1877 use Try::Tiny;
  1         1803  
  1         2091  
30              
31              
32             # ****************************************************************
33             # version
34             # ****************************************************************
35              
36             our $VERSION = "0.04";
37              
38              
39             # ****************************************************************
40             # constructor
41             # ****************************************************************
42              
43             sub new {
44 16     16 1 43178 my ($class, %init_arg) = @_;
45              
46 16         72 my $self = bless {}, $class;
47              
48 16 100       107 $self->sources(
49             exists $init_arg{sources} ? $init_arg{sources}
50             : ':all'
51             );
52 11 100       61 $self->target(
53             exists $init_arg{target} ? $init_arg{target}
54             : 'orthography'
55             );
56              
57 8         28 return $self;
58             }
59              
60              
61             # ****************************************************************
62             # accessor(s) for attribute(s)
63             # ****************************************************************
64              
65             sub sources {
66 55     55 1 28710 my ($self, $source_notation_candidates_ref) = @_;
67              
68 55 100       171 if (scalar @_ > 1) { # $self->sources(undef) comes here and dies
69 30 100 100     206 if (
70             defined $source_notation_candidates_ref &&
71             $source_notation_candidates_ref eq ':all'
72             ) {
73 90         282 $self->{sources} = [
74             grep {
75 9         367 $_ ne 'orthography';
76 9         15 } keys %{ $self->_notation }
77             ];
78             }
79             else {
80             try {
81 21     21   2479 $self->_check_source_notations($source_notation_candidates_ref);
82             }
83             catch {
84 10     10   18626 confess "Could not set source notations because: " . $_;
85 21         286 };
86 11         295 $self->{sources} = [ uniq @$source_notation_candidates_ref ];
87             }
88             }
89              
90 45         933 return $self->{sources};
91             }
92              
93             sub target {
94 47     47 1 24239 my ($self, $target_notation_candidate) = @_;
95              
96 47 100       139 if (scalar @_ > 1) { # $self->target(undef) comes here and dies
97             try {
98 23     23   1993 $self->_check_notations($target_notation_candidate);
99             }
100             catch {
101 6     6   136 confess "Could not set a target notation because: " . $_;
102 23         193 };
103 17         400 $self->{target} = $target_notation_candidate;
104             }
105              
106 41         984 return $self->{target};
107             }
108              
109              
110             # ****************************************************************
111             # utility(-ies) for attribute(s)
112             # ****************************************************************
113              
114             sub all_sources {
115 15     15 1 4061 my $self = shift;
116              
117 15         21 return @{ $self->{sources} };
  15         131  
118             }
119              
120             sub add_sources {
121 5     5 1 10445 my ($self, @adding_notations) = @_;
122              
123             try {
124 5     5   617 $self->_check_notations(@adding_notations);
125             }
126             catch {
127 3     3   150 confess "Could not add source notations because: " . $_;
128 5         44 };
129 2         46 @{ $self->{sources} } = uniq $self->all_sources, @adding_notations;
  2         10  
130              
131 2         20 return $self->{sources};
132             }
133              
134             sub remove_sources {
135 6     6 1 16363 my ($self, @removing_notations) = @_;
136              
137             try {
138 6     6   645 $self->_check_notations(@removing_notations);
139              
140             # Note: I dare do not use List::Compare to get complement notations
141 3         53 my %removing_notation;
142 3         15 @removing_notation{ @removing_notations } = ();
143 16         37 $self->{sources} = [
144             grep {
145 3         12 ! exists $removing_notation{$_};
146             } $self->all_sources
147             ];
148              
149 3         140 die 'Converter must maintain at least one source notation'
150 3 100       7 unless @{ $self->{sources} };
151             }
152             catch {
153 4     4   92 confess "Could not remove source notations because: " . $_;
154 6         68 };
155              
156 2         45 return $self->{sources};
157             }
158              
159              
160             # ****************************************************************
161             # converter(s)
162             # ****************************************************************
163              
164             sub convert {
165 25     25 1 3028 my ($self, $string) = @_;
166              
167 25 100       135 confess sprintf 'Could not convert string because '
168             . 'string (%s) must be a primitive value',
169             neat($string)
170             unless is_value($string);
171              
172 23         37 my $source_pattern = $self->_source_pattern( @{ $self->sources } );
  23         63  
173 23         47318 my $target_character = $self->_target_character( $self->target );
174              
175 23         2709 $string =~ s{
176             ($source_pattern)
177             }{$target_character->{$1}}xmsg;
178              
179 23         251 return $string;
180             }
181              
182              
183             # ****************************************************************
184             # checker(s)
185             # ****************************************************************
186              
187             sub _check_notations {
188             my ($self, @notation_candidates) = @_;
189              
190             my $notation_ref = $self->_notation;
191              
192             map {
193             die sprintf 'Notation (%s) must be a primitive value',
194             neat($_)
195             unless is_value($_);
196              
197             die sprintf 'Notation (%s) does not enumerated',
198             neat($_)
199             unless exists $notation_ref->{$_};
200             } @notation_candidates;
201              
202             return;
203             }
204              
205             sub _check_source_notations {
206             my ($self, $source_notation_candidates_ref) = @_;
207              
208             confess 'Source notations must be an array reference'
209             unless is_array_ref($source_notation_candidates_ref);
210             confess 'Source notations must be a nonnull array reference'
211             unless @$source_notation_candidates_ref;
212              
213             $self->_check_notations(@$source_notation_candidates_ref);
214              
215             return;
216             }
217              
218              
219             # ****************************************************************
220             # internal properties
221             # ****************************************************************
222              
223             sub _notation {
224             return {
225             orthography => [( # LATIN (CAPITAL|SMALL) LETTER ...
226             "\x{108}", "\x{109}", # ... C WITH CIRCUMFLEX
227             "\x{11C}", "\x{11D}", # ... G WITH CIRCUMFLEX
228             "\x{124}", "\x{125}", # ... H WITH CIRCUMFLEX
229             "\x{134}", "\x{135}", # ... J WITH CIRCUMFLEX
230             "\x{15C}", "\x{15D}", # ... S WITH CIRCUMFLEX
231             "\x{16C}", "\x{16D}", # ... U WITH BREVE
232             )],
233             zamenhof => [qw(Ch ch Gh gh Hh hh Jh jh Sh sh U u )],
234             capital_zamenhof => [qw(CH ch GH gh HH hh JH jh SH sh U u )],
235             postfix_h => [qw(Ch ch Gh gh Hh hh Jh jh Sh sh Uw uw)],
236             postfix_capital_h => [qw(CH ch GH gh HH hh JH jh SH sh UW uw)],
237             postfix_x => [qw(Cx cx Gx gx Hx hx Jx jx Sx sx Ux ux)],
238             postfix_capital_x => [qw(CX cx GX gx HX hx JX jx SX sx UX ux)],
239             postfix_caret => [qw(C^ c^ G^ g^ H^ h^ J^ j^ S^ s^ U^ u^)],
240             postfix_apostrophe => [qw(C' c' G' g' H' h' J' j' S' s' U' u')],
241             prefix_caret => [qw(^C ^c ^G ^g ^H ^h ^J ^j ^S ^s ^U ^u)],
242             };
243             }
244              
245             sub _source_pattern {
246             my ($self, @source_notations) = @_;
247              
248             my $regexp_assembler = Regexp::Assemble->new;
249             my $notation_ref = $self->_notation;
250              
251             SOURCE_NOTATION:
252             foreach my $source_notation (@source_notations) {
253             SOURCE_CHARACTER:
254             foreach my $source_character (
255             @{ $notation_ref->{ $source_notation } }
256             ) {
257             next SOURCE_CHARACTER
258             if $source_character =~ m{ \A [Uu] \z }xms;
259             ( my $escaped_source_character = $source_character )
260             =~ s{ (?=[\^\*\+]) }{\\}xms;
261             $regexp_assembler->add($escaped_source_character);
262             }
263             }
264              
265             return $regexp_assembler->re;
266             }
267              
268             sub _target_character {
269             my ($self, $target_notation) = @_;
270              
271             return ( $self->_converter_table )->{$target_notation};
272             }
273              
274             # Returns table as {$target_notation}{'source_character'} => 'target_character'
275             sub _converter_table {
276             my $self = shift;
277              
278             my $converter_table;
279             my $source_notations_ref = $self->_notation;
280             my $target_notation_ref = { %$source_notations_ref };
281              
282             TARGET_NOTATION:
283             while (
284             my ($target_notation, $target_characters_ref)
285             = each %$target_notation_ref
286             ) {
287             SOURCE_NOTATION:
288             while (
289             my ($source_notation, $source_characters_ref)
290             = each %$source_notations_ref
291             ) {
292             next SOURCE_NOTATION
293             if $source_notation eq $target_notation;
294              
295             SOURCE_CHARACTER:
296             foreach my $index ( 0 .. $#{$source_characters_ref} ) {
297             next SOURCE_CHARACTER
298             if $source_characters_ref->[$index]
299             =~ m{ \A [Uu] \z }xms;
300             $converter_table->{ $target_notation }
301             { $source_characters_ref->[$index] }
302             = $target_characters_ref->[$index];
303             }
304             }
305             }
306              
307             return $converter_table;
308             }
309              
310              
311             # ****************************************************************
312             # memoization
313             # ****************************************************************
314              
315             sub _memoize_methods {
316 6         16042 map {
317 1     1   1411 memoize $_
318             } qw(
319             _check_notations
320             _check_source_notations
321             _notation
322             _source_pattern
323             _target_character
324             _converter_table
325             );
326              
327 1         557 return;
328             }
329              
330              
331             # ****************************************************************
332             # compile-time process(es)
333             # ****************************************************************
334              
335             __PACKAGE__->_memoize_methods;
336              
337              
338             # ****************************************************************
339             # return true
340             # ****************************************************************
341              
342             1;
343             __END__