File Coverage

blib/lib/Spreadsheet/Reader/Format/ParseExcelFormatStrings.pm
Criterion Covered Total %
statement 651 708 91.9
branch 331 420 78.8
condition 120 203 59.1
subroutine 53 54 98.1
pod 2 2 100.0
total 1157 1387 83.4


line stmt bran cond sub pod time code
1             package Spreadsheet::Reader::Format::ParseExcelFormatStrings;
2             our $AUTHORITY = 'cpan:JANDREW';
3 2     2   1454 use version; our $VERSION = version->declare('v0.6.4');
  2         2  
  2         14  
4             ###LogSD warn "You uncovered internal logging statements for Spreadsheet::Reader::Format::ParseExcelFormatStrings-$VERSION";
5              
6 2     2   195 use 5.010;
  2         6  
7 2     2   7 use Moose::Role;
  2         3  
  2         12  
8             requires qw( get_defined_excel_format );
9             ###LogSD requires 'get_all_space',
10             ;
11 2         21 use Types::Standard qw(
12             Int Str Maybe
13             Num HashRef ArrayRef
14             CodeRef Object ConsumerOf
15             InstanceOf HasMethods Bool
16             is_Object is_Num is_Int
17 2     2   6721 );
  2         2  
18 2     2   3082 use Carp qw( confess longmess );
  2         3  
  2         103  
19 2     2   8 use Type::Coercion;
  2         2  
  2         34  
20 2     2   7 use Type::Tiny;
  2         2  
  2         35  
21 2     2   879 use DateTimeX::Format::Excel 0.014;
  2         683922  
  2         96  
22 2     2   1183 use DateTime::Format::Flexible;
  2         199090  
  2         19  
23 2     2   187 use DateTime;
  2         2  
  2         49  
24 2     2   7 use Clone 'clone';
  2         2  
  2         126  
25 2     2   7 use lib '../../../../lib',;
  2         2  
  2         16  
26             #~ ###LogSD use Capture::Tiny 'capture_stderr';
27             ###LogSD use Log::Shiras::Telephone;
28             ###LogSD use Log::Shiras::Unhide;
29 2         15 use Spreadsheet::Reader::Format::Types qw(
30             PositiveNum NegativeNum ZeroOrUndef
31             NotNegativeNum Excel_number_0
32 2     2   1287 );#
  2         15  
33              
34             #########1 Dispatch Tables & Package Variables 5#########6#########7#########8#########9
35              
36             my $coercion_index = 0;
37             my @type_list = ( PositiveNum, NegativeNum, ZeroOrUndef, Str );
38             my $last_date_cldr = 'yyyy-mm-dd';# This is critical to getting the next string to date conversion right
39             my $last_duration = 0;
40             my $last_sub_seconds = 0;
41             my $last_format_rem = 0;
42             my $duration_order ={ h => 'm', m =>'s', s =>'0' };
43             my $number_build_dispatch ={
44             all =>[qw(
45             _convert_negative
46             _divide_by_thousands
47             _convert_to_percent
48             _split_decimal_integer
49             _move_decimal_point
50             _build_fraction
51             _round_decimal
52             _add_commas
53             _pad_exponent
54             )],
55             scientific =>[qw(
56             _convert_negative
57             _split_decimal_integer
58             _move_decimal_point
59             _round_decimal
60             _add_commas
61             _pad_exponent
62             )],
63             percent =>[qw(
64             _convert_negative
65             _convert_to_percent
66             _split_decimal_integer
67             _round_decimal
68             _add_commas
69             )],
70             fraction =>[qw(
71             _convert_negative
72             _split_decimal_integer
73             _build_fraction
74             _add_commas
75             )],
76             integer =>[qw(
77             _convert_negative
78             _divide_by_thousands
79             _split_decimal_integer
80             _round_decimal
81             _add_commas
82             )],
83             decimal =>[qw(
84             _convert_negative
85             _divide_by_thousands
86             _split_decimal_integer
87             _round_decimal
88             _add_commas
89             )],
90             };
91              
92             #########1 Public Attributes 3#########4#########5#########6#########7#########8#########9
93              
94             has workbook_inst =>(
95             isa => 'Spreadsheet::Reader::ExcelXML::Workbook',
96             handles =>[ qw( set_error get_epoch_year )],
97             writer => 'set_workbook_inst',
98             predicate => '_has_workbook_inst',
99             );
100              
101             has cache_formats =>(
102             isa => Bool,
103             reader => 'get_cache_behavior',
104             writer => 'set_cache_behavior',
105             default => 1,
106             );
107              
108             has datetime_dates =>(
109             isa => Bool,
110             reader => 'get_date_behavior',
111             writer => 'set_date_behavior',
112             default => 0,
113             );
114              
115             has european_first =>(
116             isa => Bool,
117             reader => 'get_european_first',
118             writer => 'set_european_first',
119             default => 0,
120             );
121              
122             #########1 Public Methods 3#########4#########5#########6#########7#########8#########9
123              
124             sub get_defined_conversion{# Add a test for this function in 08-parse...
125 38     38 1 15216 my( $self, $position, $target_name ) = @_;
126             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
127             ###LogSD $self->get_all_space . '::get_defined_conversion', );
128             ###LogSD $phone->talk( level => 'debug', message => [
129             ###LogSD "Searching for the coercion for position: $position", ($target_name ? "With suggested name: $target_name" : '') ] );
130 38         142 my $coercion_string = $self->get_defined_excel_format( $position );
131 38 50       116 if( !defined $coercion_string ){
132 0         0 $self->set_error( "No coercion available for position: $position" );
133 0         0 return undef;
134             }
135             ###LogSD $phone->talk( level => 'debug', message => [
136             ###LogSD "Position -$position- is associated with the string: $coercion_string", ] );
137 38   66     219 my $coercion = $self->parse_excel_format_string( $coercion_string, ($target_name//"Excel__$position") );
138 38 50       119 if( !$coercion ){
139 0         0 $self->set_error( "Unparsable conversion string at position -$position- found: $coercion_string" );
140 0         0 return undef;
141             }
142             ###LogSD my $level =
143             #~ ###LogSD $position == 164 ? 'fatal' :
144             ###LogSD 'trace';
145             ###LogSD $phone->talk( level => $level, message => [
146             ###LogSD 'Returning coercion:', $coercion,] );
147 38         337 return $coercion;
148             }
149              
150             sub parse_excel_format_string{
151 105     105 1 16426 my( $self, $format_strings, $coercion_name ) = @_;
152             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
153             ###LogSD $self->get_all_space . '::parse_excel_format_string', );
154 105 50       255 if( !defined $format_strings ){
155             ###LogSD $phone->talk( level => 'info', message => [
156             ###LogSD "Nothing passed to convert",] );
157 0         0 return Excel_number_0;
158             }
159 105         276 $format_strings =~ s/\\//g;
160             ###LogSD $phone->talk( level => 'info', message => [
161             ###LogSD "parsing the custom excel format string: $format_strings",
162             ###LogSD ($coercion_name ? "With passed name: $coercion_name" : '')] );
163 105         144 my $conversion_type = 'number';
164             # Check the cache
165 105         96 my $cache_key;
166 105 50       4191 if( $self->get_cache_behavior ){
167             ###LogSD $phone->talk( level => 'debug', message => [
168             ###LogSD "checking stored cache of the key: $format_strings",
169             ###LogSD '..searching in stored keys:', keys %{$self->_get_all_format_cache} ] );
170 105         198 $cache_key = $format_strings; # TODO fix the non-hashkey character issues;
171 105 100       4152 if( $self->_has_cached_format( $cache_key ) ){
172             ###LogSD $phone->talk( level => 'debug', message => [
173             ###LogSD "Format already built - returning stored value for: $cache_key", ] );
174 6         240 return $self->_get_cached_format( $cache_key );
175             }else{
176             ###LogSD $phone->talk( level => 'debug', message => [
177             ###LogSD "Building new format for key: $cache_key", ] );
178             }
179             }
180              
181             # Split into the four sections positive, negative, zero, and text
182 99         235 $format_strings =~ s/General/\@/ig;# Change General to text input
183 99         358 my @format_string_list = split /;/, $format_strings;
184 99 100       429 my $last_is_text = ( $format_string_list[-1] =~ /^[A-Za-z\@\s\"]*$/ ) ? 1 : 0 ;
185             ###LogSD $phone->talk( level => 'debug', message => [
186             ###LogSD "Is the last position text: $last_is_text", scalar( @format_string_list ) ] );
187             # Make sure the full range of number inputs are sent down the right path;
188 99         133 my @used_type_list = @{\@type_list};
  99         312  
189 99 100       236 pop @used_type_list if !$last_is_text;
190 99 100       392 if( scalar( @format_string_list ) == ( 2 + $last_is_text ) ){
    100          
    100          
191 17         63 $used_type_list[0] = NotNegativeNum;#Maybe[NotNegativeNum];
192 17         75 splice( @used_type_list, 2, 1 );
193             }elsif( scalar( @format_string_list ) == ( 1 + $last_is_text ) ){
194 70         319 $used_type_list[0] = Num;#Maybe[Num];
195 70         368 splice( @used_type_list, 1, 2 );
196             }elsif( scalar( @format_string_list ) == ( 0 + $last_is_text ) ){# Generally only true for text only formats
197 4         9 @used_type_list = ();
198 4         23 $used_type_list[0] = Str;
199 4         17 $conversion_type = 'text';
200             }
201             ###LogSD $phone->talk( level => 'debug', message => [
202             ###LogSD "Now operating on each format string", @format_string_list,
203             ###LogSD '..with used type list:', map{ $_->name } @used_type_list, ] );
204 99         155 my $format_position = 0;
205 99         113 my @coercion_list;
206             my $action_type;
207 99         124 my $is_date = 0;
208 99         122 my $date_text = 0;
209 99         87 my $last_deconstructed_list;
210 99         193 for my $format_string ( @format_string_list ){
211 145         298 $format_string =~ s/_.//g;# no character justification to other rows
212 145         222 $format_string =~ s/\*//g;# Remove the repeat character listing (not supported here)
213             ###LogSD $phone->talk( level => 'debug', message => [
214             ###LogSD "Building format for: $format_string", ] );
215              
216             # Pull out all the straight through stuff
217 145         127 my @deconstructed_list;
218 145         139 my $x = 0;
219             #~ $action_type = undef;
220 145   33     1499 while( defined $format_string and my @result = $format_string =~
221             /^( # Collect any formatting stuff first
222             (AM\/PM| # Date 12 hr flag
223             A\/P| # Another date 12 hr flag
224             \[hh?\]| # Elapsed hours
225             \[mm\]| # Elapsed minutes
226             \[ss\]| # Elapsed seconds
227             [dmyhms]+)| # DateTime chunks
228             ([0-9#\?]+[,\-\_]?[#0\?]*,*| # Number string
229             \.| # Split integers from decimals
230             [Ee][+\-]| # Exponential notiation
231             %)| # Percentage
232             (\@) # Text input
233             )?( # Finish collecting format actions
234             (\"[^\"]*\")| # Anything in quotes just passes through
235             (\[[^\]]*\])| # Anything in brackets needs modification
236             [\(\)\$\-\+\/\:\!\^\&\'\~\{\}\<\>\=\s]| # All the pass through characters
237             \,\s # comma space for verbal separation
238             )?(.*)/x ){
239             ###LogSD $phone->talk( level => 'debug', message => [
240             ###LogSD "Now processing: $format_string", '..with result:', @result ] );
241 459         597 my $pre_action = $1;
242 459         397 my $date = $2;
243 459         416 my $number = $3;
244 459         433 my $text = $4;
245 459         412 my $fixed_value = $5;
246 459         360 my $quote_string = $6;
247 459         469 $format_string = $8;
248 459 100       611 if( $fixed_value ){
249 195 100 100     778 if( $fixed_value =~ /\[\$([^\-\]]*)\-?\d*\]/ ){# removed the localized element of fixed values
    100          
    100          
250 4         6 $fixed_value = $1;
251             }elsif( $fixed_value =~ /\[[^hms]*\]/ ){# Remove all color and conditionals as they will not be used
252 8         13 $fixed_value = undef;
253             }elsif( $fixed_value =~ /\"\-\"/ and $format_string ){# remove decimal justification for zero bars
254             ###LogSD $phone->talk( level => 'trace', message => [
255             ###LogSD "Initial format string: $format_string", ] );
256 4         12 $format_string =~ s/^(\?+)//;
257             ###LogSD $phone->talk( level => 'trace', message => [
258             ###LogSD "updated format string: $format_string", ] );
259             }
260             }
261 459 100       548 if( defined $pre_action ){
262 324 50       552 my $current_action =
    100          
    100          
263             ( $date ) ? 'DATE' :
264             ( defined $number ) ? 'NUMBER' :
265             ( $text ) ? 'TEXT' : 'BAD' ;
266 324 100       469 $is_date = 1 if $date;
267             ###LogSD $phone->talk( level => 'debug', message => [
268             ###LogSD "Current action from -$pre_action- is: $current_action",
269             ###LogSD "..now testing against: " . ($action_type//'') ] );
270 324 100 66     1466 if( $action_type and $current_action and ($current_action ne $action_type) and $action_type ne 'TEXT' ){
      100        
      66        
271             ###LogSD $phone->talk( level => 'info', message => [
272             ###LogSD "General action type: $action_type",
273             ###LogSD "is failing current action: $current_action", ] );
274 33         32 my $fail = 1;
275 33 100 66     169 if( $action_type eq 'DATE' ){
    50          
    50          
276 8         12 $conversion_type = 'date';
277             ###LogSD $phone->talk( level => 'info', message => [
278             ###LogSD "Checking the date mishmash", ] );
279 8 100       34 if( $current_action eq 'NUMBER' ){
    50          
280             ###LogSD $phone->talk( level => 'info', message => [
281             ###LogSD "Special case of number following action", ] );
282 4 50 66     54 if( ( $pre_action =~ /^\.$/ and $format_string =~ /^0+/ ) or
      33        
      66        
283             ( $pre_action =~ /^0+$/ and $deconstructed_list[-1]->[0] =~ /^\.$/ ) ){
284 4         7 $current_action = 'DATE';
285 4         7 $fail = 0;
286             }
287             }elsif( $pre_action eq '@' ){
288             ###LogSD $phone->talk( level => 'info', message => [
289             ###LogSD "Excel conversion of pre-epoch datestring pass through highjacked here", ] );
290 4         6 $current_action = 'DATESTRING';
291 4         4 $fail = 0;
292             }
293             }elsif( $action_type eq 'NUMBER' ){
294             ###LogSD $phone->talk( level => 'info', message => [
295             ###LogSD "Checking for possible number field exceptions", ] );
296 0 0       0 if( $current_action eq 'TEXT' ){
297             ###LogSD $phone->talk( level => 'info', message => [
298             ###LogSD "Special case of text following a number", ] );
299 0         0 $fail = 0;
300             }
301             }elsif( $action_type eq 'INTEGER' or $action_type eq 'DECIMAL'){
302             ###LogSD $phone->talk( level => 'info', message => [
303             ###LogSD "Checking for possible sub-Number generalities", ] );
304 25 50       47 if( $current_action eq 'NUMBER' ){
305             ###LogSD $phone->talk( level => 'info', message => [
306             ###LogSD "Integers are numbers", ] );
307 25         31 $fail = 0;
308             }
309             }
310 33 50       64 if( $fail ){
311 0         0 confess "Bad combination of actions in this format string: $format_strings - $action_type - $current_action";
312             }
313             }
314 324 50       581 $action_type = $current_action if $current_action;
315             ###LogSD $phone->talk( level => 'debug', message => [
316             ###LogSD (($pre_action) ? "First action resolved to: $pre_action" : undef),
317             ###LogSD (($fixed_value) ? "Extracted fixed value: $fixed_value" : undef),
318             ###LogSD (($format_string) ? "Remaining string: $format_string" : undef),
319             ###LogSD "With updated deconstruction list:", @deconstructed_list, ] );
320             }else{
321             ###LogSD $phone->talk( level => 'debug', message => [
322             ###LogSD "Early elements unusable - remaining string: $fixed_value", $quote_string ] );
323 135 100 66     239 $action_type = 'TEXT' if $quote_string and $fixed_value eq $quote_string;
324             }
325 459         755 push @deconstructed_list, [ $pre_action, $fixed_value ];
326 459 100       728 if( $x++ == 30 ){
327 2         42 confess "Attempts to use string |$format_string| as an excel custom format failed. Contact the author jandrew\@cpan (.org) if you feel there is an error";
328             }
329             ###LogSD $phone->talk( level => 'debug', message => [
330             ###LogSD (($pre_action) ? "First action resolved to: $pre_action" : undef),
331             ###LogSD (($fixed_value) ? "Extracted fixed value: $fixed_value" : undef),
332             ###LogSD (($format_string) ? "Remaining string: $format_string" : undef),
333             ###LogSD "With updated deconstruction list:", @deconstructed_list, ] );
334 457 100       2845 last if length( $format_string ) == 0;
335             }
336             ###LogSD $phone->talk( level => 'debug', message => [
337             ###LogSD "Handling action type: $action_type", ] );
338 143 50       277 push @deconstructed_list, [ $format_string, undef ] if $format_string;
339             ###LogSD $phone->talk( level => 'debug', message => [
340             ###LogSD "List with fixed values separated:", @deconstructed_list ] );
341 143 100       551 my $method = '_build_' . ( $action_type =~ /^(NUMBER|SCIENTIFIC|INTEGER|PERCENT|FRACTION|DECIMAL)$/ ? 'number' : lc($action_type) );
342             ###LogSD $phone->talk( level => 'info', message => [ "Method: $method", ] );
343 143 100 66     600 my $filter = ( $action_type and $action_type eq 'TEXT' ) ? Str : $used_type_list[$format_position++];
344 143 100 66     592 if( $action_type and $action_type eq 'DATESTRING' ){
345             ###LogSD $phone->talk( level => 'trace', message => [
346             ###LogSD "Found date string and making adjustments with:", @deconstructed_list, $last_deconstructed_list ] );
347 4         5 $date_text = 1;
348 4         13 $filter = Str;
349 4 50       23 @deconstructed_list = @$last_deconstructed_list if $last_deconstructed_list;
350             ###LogSD $phone->talk( level => 'debug', message => [
351             ###LogSD "Updated deconstructed list:", @deconstructed_list, ] );
352             }
353 143         2307 $last_deconstructed_list = clone( [@deconstructed_list] );
354              
355             ###LogSD $phone->talk( level => 'debug', message => [
356             ###LogSD "Running method -$method- for list:", @deconstructed_list ] );
357 143         693 my( $intermediate_action, @intermediate_coercions ) = $self->$method( $filter, \@deconstructed_list, $format_string );
358             ###LogSD $phone->talk( level => 'trace', message => [ "Returning from: $method", $intermediate_action, @intermediate_coercions ] );
359 143         276 push @coercion_list, @intermediate_coercions;
360 143 100       878 $action_type = $intermediate_action =~ /^(NUMBER|SCIENTIFIC|INTEGER|PERCENT|FRACTION|DECIMAL|DATE|DATESTRING)$/ ? $intermediate_action : $action_type;
361             ###LogSD $phone->talk( level => 'trace', message => [ "Action type: $action_type", $intermediate_action, @coercion_list ] );
362             }
363 97 100 100     339 if( $is_date and !$date_text ){
364 26         127 ( my $intermediate_action, my @intermediate_coercions ) = $self->_build_datestring( Str, $last_deconstructed_list );
365 26         48 push @coercion_list, @intermediate_coercions;
366 26 50       164 $action_type = $intermediate_action =~ /^(NUMBER|SCIENTIFIC|INTEGER|PERCENT|FRACTION|DECIMAL|DATE|DATESTRING)$/ ? $intermediate_action : $action_type;
367             ###LogSD $phone->talk( level => 'info', message => [ "Adjusted action type: $action_type", ] );
368             }
369             ###LogSD $phone->talk( level => 'debug', message => [
370             ###LogSD 'Length of coersion list: ' . scalar( @coercion_list ),
371             ###LogSD "Action type: $action_type", "Conversion type: $conversion_type", ] );
372             ###LogSD $phone->talk( level => 'trace', message => [
373             ###LogSD ($coercion_name ? "Initial coercion name: $coercion_name" : ''), ] );
374              
375             # Build the final format
376             #~ $conversion_type = 'text' if $action_type eq 'TEXT';
377 97 100       369 $coercion_name =~ s/__/_${conversion_type}_/ if $coercion_name;
378             ###LogSD $phone->talk( level => 'info', message => [ "Action type: $action_type" ] );
379 97   66     981 my %args = (
380             name => $action_type,
381             display_name => ($coercion_name // ($action_type . '_' . $coercion_index++)),
382             coercion => Type::Coercion->new(
383             type_coercion_map => [ @coercion_list ],
384             ),
385             #~ coerce => 1,
386             );
387 97         14050 my $final_type = Type::Tiny->new( %args );
388             ###LogSD $phone->talk( level => 'trace', message => [
389             ###LogSD "Final type:", $final_type ] );
390              
391             # Save the cache
392 97 50       10115 if( $self->get_cache_behavior ){
393             ###LogSD $phone->talk( level => 'debug', message => [
394             ###LogSD "setting cache for key:", $cache_key ] );
395 97         3837 $self->_set_cashed_format( $cache_key => $final_type );
396             }
397              
398 97         673 return $final_type;
399             }
400              
401              
402             #########1 Private Attributes 3#########4#########5#########6#########7#########8#########9
403              
404             has _format_cash =>(
405             isa => HashRef,
406             traits => ['Hash'],
407             reader => '_get_all_format_cache',
408             handles =>{
409             _has_cached_format => 'exists',
410             _get_cached_format => 'get',
411             _set_cashed_format => 'set',
412             },
413             default => sub{ {} },
414             );
415              
416             has _alt_epoch_year =>(
417             isa => Int,
418             reader => '_get_alt_epoch_year',
419             default => 1900,
420             );
421              
422             #########1 Private Methods 3#########4#########5#########6#########7#########8#########9
423              
424             sub _build_text{
425 21     21   35 my( $self, $type_filter, $list_ref ) = @_;
426             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
427             ###LogSD $self->get_all_space . '::hidden::_build_text', );
428             ###LogSD $phone->talk( level => 'debug', message => [
429             ###LogSD "Building an anonymous sub to process text values", $list_ref ] );
430 21         23 my $sprintf_string;
431 21         20 my $found_string = 0;
432 21         22 my $alt_string;
433 21         36 for my $piece ( @$list_ref ){
434             ###LogSD $phone->talk( level => 'debug', message => [
435             ###LogSD "processing text piece:", $piece ] );
436 25 100 66     111 if( !$found_string and defined $piece->[0] ){
437             ###LogSD $phone->talk( level => 'debug', message => [
438             ###LogSD "The first element is defined:", $piece->[0] ] );
439 12         17 $sprintf_string .= '%s';
440 12         13 $found_string = 1;
441             }
442 25 100       50 if( $piece->[1] ){
443 13         44 $piece->[1] =~ s/\"(.*)\"/$1/g;# Remove quotes around text strings (mostly spaces?)
444             ###LogSD $phone->talk( level => 'debug', message => [
445             ###LogSD "Adding fixed value |$piece->[1]|" ] );
446 13         17 $sprintf_string .= $piece->[1];
447 13         22 $alt_string .= $piece->[1];
448             }
449             }
450             ###LogSD $phone->talk( level => 'debug', message => [
451             ###LogSD "Final sprintf string: $sprintf_string",
452             ###LogSD "Final alt string: " . ($alt_string//'undef') ] );
453 21         27 my $return_sub;
454             ###LogSD my $sub_phone = Log::Shiras::Telephone->new( name_space =>
455             ###LogSD $self->get_log_space . '::Cell::_hidden::_return_value_only::_build_text', );
456 21 100       39 if( $found_string ){
457             $return_sub = sub{
458             #~ print "Using Text sub!!!!!!\n";
459             ###LogSD $sub_phone->talk( level => 'debug', message => [
460             ###LogSD "Updated Input: $_[0]" ] );
461 8     8   3471 return sprintf( $sprintf_string, $_[0] );
462 12         44 };
463             }else{
464             $return_sub = sub{
465             #~ print "Using Text sub!!!!!!\n";
466             ###LogSD $sub_phone->talk( level => 'debug', message => [
467             ###LogSD "Received Input |$_[0]| -> returning $alt_string" ] );
468 2     2   1054 return $alt_string;
469 9         29 };
470             }
471 21         46 return( 'TEXT', Str, $return_sub );
472             }
473              
474             sub _build_date{
475 30     30   50 my( $self, $type_filter, $list_ref ) = @_;
476             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
477             ###LogSD $self->get_all_space . '::hidden::_build_date', );
478             ###LogSD $phone->talk( level => 'debug', message => [
479             ###LogSD "Building an anonymous sub to process date values", $list_ref ] );
480              
481 30         31 my $build_ref;#
482 30         107 @$build_ref{qw( is_duration sub_seconds )} = ( 0, 0 );
483             # Process once to build the cldr string and other flags
484 30         111 @$build_ref{qw( cldr_string is_duration sub_seconds format_remainder )} = $self->_build_date_cldr( $list_ref );
485 30         275 my $clone_ref = clone( $build_ref );
486             ###LogSD $phone->talk( level => 'debug', message => [
487             ###LogSD "Conversion sub will use ref: ", $clone_ref ] );
488 30 50       186 my @args_list = ( $self->get_epoch_year == 1904 ) ? ( system_type => 'apple_excel' ) : ();
489 30         2757 my $converter = DateTimeX::Format::Excel->new( @args_list );
490             ###LogSD $phone->talk( level => 'debug', message => [
491             ###LogSD "Building sub with:", @args_list, "And get_date_behavior set to: " . $self->get_date_behavior ] );
492             my $conversion_sub = sub{
493 172     172   94867 my $num = $_[0];
494 172 50       435 if( !defined $num ){
495 0         0 return undef;
496             }
497             #~ print "Using Date sub!!!!!!\n";
498             ###LogSD my $sub_phone = Log::Shiras::Telephone->new( name_space =>
499             ###LogSD $self->get_log_space . '::Cell::_hidden::_return_value_only::_build_date', );
500             ###LogSD $sub_phone->talk( level => 'debug', message => [
501             ###LogSD "Processing date number: $num",
502             ###LogSD '..with adjustement ref:', $clone_ref, ] );
503 172         584 my $dt = $converter->parse_datetime( $num );
504 172         129336 my $return_string;
505             my $calc_sub_secs;
506 172 100       412 if( $clone_ref->{is_duration} ){
507 12         364 my $di = $dt->subtract_datetime_absolute( $converter->_get_epoch_start );
508 12 50       1382 if( $self->get_date_behavior ){
509 0         0 return $di;
510             }
511 12         305 my $sign = DateTime->compare_ignore_floating( $dt, $converter->_get_epoch_start );
512 12 50       648 $return_string = ( $sign == -1 ) ? '-' : '' ;
513 12         19 my $key = $clone_ref->{is_duration}->[0];
514 12         27 my $delta_seconds = $di->seconds;
515 12         227 my $delta_nanosecs = $di->nanoseconds;
516 12         189 $return_string .= $self->_build_duration( $clone_ref->{is_duration}, $delta_seconds, $delta_nanosecs );
517             ###LogSD $sub_phone->talk( level => 'debug', message => [
518             ###LogSD "Duration return string: $return_string" ] );
519             }else{
520 160 100       6370 if( $self->get_date_behavior ){
521             ###LogSD $sub_phone->talk( level => 'debug', message => [
522             ###LogSD "Returning the DateTime object rather than the format string" ] );
523 4         13 return $dt;
524             }
525 156 100       344 if( $clone_ref->{sub_seconds} ){
526 12         31 $calc_sub_secs = $dt->format_cldr( $clone_ref->{sub_seconds} );
527             ###LogSD $sub_phone->talk( level => 'debug', message => [
528             ###LogSD "Processing sub-seconds: $calc_sub_secs" ] );
529 12 100       1238 if( "0.$calc_sub_secs" >= 0.5 ){
530             ###LogSD $phone->talk( level => 'debug', message => [
531             ###LogSD "Rounding seconds back down" ] );
532 10         25 $dt->subtract( seconds => 1 );
533             }
534             }
535             ###LogSD $sub_phone->talk( level => 'debug', message => [
536             ###LogSD "Converting it with CLDR string: " . ($clone_ref->{cldr_string}//'undef') ] );
537 156 50       6508 if( $clone_ref->{cldr_string} ){
538 156         408 $return_string .= $dt->format_cldr( $clone_ref->{cldr_string} );
539             }else{
540 0         0 $self->set_error( "No cldr string passed where one is expected while processing |$num|" );
541 0         0 $return_string .= scalar( $dt );
542             }
543 156 100 66     29703 if( $clone_ref->{sub_seconds} and $clone_ref->{sub_seconds} ne '1' ){
544 12         14 $return_string .= $calc_sub_secs;
545             }
546 156 50       332 $return_string .= $dt->format_cldr( $clone_ref->{format_remainder} ) if $clone_ref->{format_remainder};
547             }
548             ###LogSD $sub_phone->talk( level => 'debug', message => [
549             ###LogSD "returning: $return_string" ] );
550 168         939 return $return_string;
551 30         45191 };
552 30         142 return( 'DATE', $type_filter, $conversion_sub, );
553             }
554              
555             sub _build_datestring{
556 30     30   158 my( $self, $type_filter, $list_ref ) = @_;
557             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
558             ###LogSD $self->get_all_space . '::hidden::_build_datestring', );
559             ###LogSD $phone->talk( level => 'debug', message => [
560             ###LogSD "Building an anonymous sub to process date strings", $list_ref, ] );
561              
562 30         54 my $build_ref;#
563 30         84 @$build_ref{qw( is_duration sub_seconds )} = ( 0, 0 );
564             # Process once to build the cldr string and other flags
565 30         65 @$build_ref{qw( cldr_string is_duration sub_seconds format_remainder )} = $self->_build_date_cldr( $list_ref );
566 30         298 my $clone_ref = clone( $build_ref );
567             ###LogSD $phone->talk( level => 'debug', message => [
568             ###LogSD "Conversion sub will use ref: ", $clone_ref ] );
569             my $conversion_sub = sub{
570 46     46   36331 my $date = $_[0];
571 46 50       144 if( !$date ){
572 0         0 return undef;
573             }
574             #~ print "Using DateString sub!!!!!!\n";
575             ###LogSD my $sub_phone = Log::Shiras::Telephone->new( name_space =>
576             ###LogSD $self->get_log_space . '::Cell::_hidden::_return_value_only::_build_datestring', );
577 46         66 my $calc_sub_secs;
578             ###LogSD $sub_phone->talk( level => 'debug', message => [
579             ###LogSD "Processing date: $date", "..with clone_ref:", $clone_ref] );
580 46 100       255 if( $date =~ /(.*:\d+)\.(\d+)(.*)/ ){
581 28         78 $calc_sub_secs = $2;
582             ###LogSD $sub_phone->talk( level => 'debug', message => [
583             ###LogSD "updated sub seconds: " . ($calc_sub_secs//'undef') ] );
584 28         58 $date = $1;
585 28 50       144 $date .= $3 if $3;
586 28         96 $calc_sub_secs .= 0 x (9 - length( $calc_sub_secs ));
587             }
588             ###LogSD $sub_phone->talk( level => 'debug', message => [
589             ###LogSD "Processing new date string: $date",
590             ###LogSD "..with stripped nanoseconds: " . ($calc_sub_secs//'undef'),
591             ###LogSD "..against date return behaviour: " . $self->get_date_behavior,
592             ###LogSD "..and format ref:", $clone_ref ] );
593 46         64 my ( $dt_us, $dt_eu );
594 46         3018 eval '$dt_us = DateTime::Format::Flexible->parse_datetime( $date )';
595 46         206993 eval '$dt_eu = DateTime::Format::Flexible->parse_datetime( $date, european => 1, )';
596 46 50 66     171327 if( !$dt_us and !$dt_eu ){# handle double digit years in formats unreadable by ~::Flexible
597             ###LogSD $sub_phone->talk( level => 'debug', message => [
598             ###LogSD "Initial DateTime conversion failed - attempting backup work for: $date" ] );
599 2         9 my $current_year = DateTime->now()->truncate( to => 'year' );
600 2         697 my $century_prefix = substr( $current_year, 0, 2 );
601 2         45 my $century_postfix = substr( $current_year, 2, 2 );
602 2 50       30 my $bump_year = ( $century_postfix + 20 > 99 ) ? ( $century_postfix - 80 ) : undef;# The double digit years are probably less than 21 years in the future of the processing time
603 2 50       8 my $drop_year = ( $century_postfix - 79 < 0 ) ? ( $century_postfix + 21 ) : undef;# The double digit years are probably less than 81 years in the past of the processing time
604 2         10 $date =~ /(\d{1,2})\D(\d{1,2})\D(\d{1,2})(\s|T)(\d{1,2})\D(\d{1,2})(\D(\d{1,2}))?/;
605 2 50 33     33 if ( defined $1 and defined $2 and defined $3 ){
      33        
606             ###LogSD $sub_phone->talk( level => 'debug', message => [
607             ###LogSD "Processing date parse for: $date", $1, $2, $3, $4, $5, $6, $7 ] );
608 2         5 my $year = $3;
609 2 50 33     27 $year = (
    50 33        
610             (defined $bump_year and $year <= $bump_year ) ? $century_prefix + 1 :
611             (defined $drop_year and $year >= $drop_year ) ? $century_prefix - 1 : $century_prefix ) . sprintf '%02u', $year;
612 2   50     20 my $us_str = sprintf "%u-%02u-%02uT%02u:%02u:%02u", $year, $1, $2, $5, $6, ($7//'00');
613 2   50     14 my $eu_str = sprintf "%u-%02u-%02uT%02u:%02u:%02u", $year, $2, $1, $5, $6, ($7//'00');
614 2         155 eval '$dt_us = DateTime::Format::Flexible->parse_datetime( $us_str )';
615 2         11506 eval '$dt_eu = DateTime::Format::Flexible->parse_datetime( $eu_str )';# european => 1,
616             }
617             }
618 46 50 66     14421 my $dt =
    100          
619             ( $self->get_european_first and $dt_eu )? $dt_eu :# DD-MM-YY tested instead of MM-DD-YY
620             ( $dt_us ) ? $dt_us : $dt_eu ;
621             ###LogSD $sub_phone->talk( level => 'debug', message => [
622             ###LogSD "Result of the processing of -$date- " . $dt ] );
623 46 50       1202 if( $dt ){
624 46 100       1069 $dt->add( nanoseconds => $calc_sub_secs ) if $calc_sub_secs;
625             ###LogSD $sub_phone->talk( level => 'debug', message => [
626             ###LogSD "Date building sucessfull - result to this point: $dt" ] );
627             }else{
628             ###LogSD $sub_phone->talk( level => 'debug', message => [
629             ###LogSD "Unable to convert the string to a date time object: $date" ] );
630 0         0 return $date;
631             }
632 46         22996 my $return_string;
633 46 100       147 if( $clone_ref->{is_duration} ){
634 2 50       18 my @args_list = ( $self->get_epoch_year == 1904 ) ? ( system_type => 'apple_excel' ) : ();
635 2         209 my $converter = DateTimeX::Format::Excel->new( @args_list );
636 2         3092 my $di = $dt->subtract_datetime_absolute( $converter->_get_epoch_start );
637 2 50       300 if( $self->get_date_behavior ){
638 0         0 return $di;
639             }
640 2         52 my $sign = DateTime->compare_ignore_floating( $dt, $converter->_get_epoch_start );
641 2 50       110 $return_string = ( $sign == -1 ) ? '-' : '' ;
642 2         7 my $key = $clone_ref->{is_duration}->[0];
643 2         10 my $delta_seconds = $di->seconds;
644 2         65 my $delta_nanosecs = $di->nanoseconds;;
645             ###LogSD $sub_phone->talk( level => 'debug', message => [
646             ###LogSD "Delta seconds: $delta_seconds",
647             ###LogSD (($delta_nanosecs) ? "Delta nanoseconds: $delta_nanosecs" : undef) ] );
648 2         44 $return_string .= $self->_build_duration( $clone_ref->{is_duration}, $delta_seconds, $delta_nanosecs );
649             ###LogSD $phone->talk( level => 'debug', message => [
650             ###LogSD "Duration return string: $return_string" ] );
651             }else{
652 44 50       1727 if( $self->get_date_behavior ){
653 0         0 return $dt;
654             }
655 44 100       145 if( $clone_ref->{sub_seconds} ){
656 2         12 $calc_sub_secs = $dt->format_cldr( $clone_ref->{sub_seconds} );
657             ###LogSD $sub_phone->talk( level => 'debug', message => [
658             ###LogSD "Processing sub-seconds: $calc_sub_secs" ] );
659 2 50       321 if( "0.$calc_sub_secs" >= 0.5 ){
660             ###LogSD $sub_phone->talk( level => 'debug', message => [
661             ###LogSD "Rounding seconds back down" ] );
662 0         0 $dt->subtract( seconds => 1 );
663             }
664             }
665 44 50       105 if( $clone_ref->{cldr_string} ){
666             ###LogSD $sub_phone->talk( level => 'debug', message => [
667             ###LogSD "Using CLDR string: $clone_ref->{cldr_string}\n", ] );
668 44         181 $return_string .= $dt->format_cldr( $clone_ref->{cldr_string} );
669             }else{
670 0         0 $self->set_error( "No cldr string passed where one is expected while processing |$date|" );
671 0         0 $return_string .= scalar( $dt );
672             }
673 44 100 66     8159 if( $clone_ref->{sub_seconds} and $clone_ref->{sub_seconds} ne '1' ){
674 2         4 $return_string .= $calc_sub_secs;
675             }
676 44 50       122 $return_string .= $dt->format_cldr( $clone_ref->{format_remainder} ) if $clone_ref->{format_remainder};
677             ###LogSD $sub_phone->talk( level => 'debug', message => [
678             ###LogSD "returning: $return_string" ] );
679             }
680 46         427 return $return_string;
681 30         237 };
682             ###LogSD $phone->talk( level => 'trace', message => [
683             ###LogSD "returning:", 'DATESTRING', $type_filter, $conversion_sub ] );
684 30         129 return( 'DATESTRING', $type_filter, $conversion_sub );
685             }
686              
687             sub _build_date_cldr{
688 60     60   82 my( $self, $list_ref ) = @_;
689             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
690             ###LogSD $self->get_all_space . '::hidden::_build_date_cldr', );
691             ###LogSD $phone->talk( level => 'debug', message => [
692             ###LogSD "transforming an excel format string to the cldr equivalent", $list_ref ] );
693              
694 60         66 my ( $cldr_string, $format_remainder );
695 60         53 my $is_duration = 0;
696 60         65 my $sub_seconds = 0;
697             # Process once to build the cldr string
698 60         57 my $prior_duration;
699 60         91 for my $piece ( @$list_ref ){
700             ###LogSD $phone->talk( level => 'debug', message => [
701             ###LogSD "processing date piece:", $piece ] );
702 192 100       310 if( defined $piece->[0] ){
703             ###LogSD $phone->talk( level => 'debug', message =>[
704             ###LogSD "Manageing the cldr part: " . $piece->[0] ] );
705 184 100 100     1058 if( $piece->[0] =~ /\[(.+)\]/ ){
    100          
    100          
    100          
    100          
    100          
    100          
    100          
706             ###LogSD $phone->talk( level => 'debug', message =>[ "Possible duration" ] );
707 4         13 (my $initial,) = split //, $1;
708 4         11 my $length = length( $1 );
709 4         14 $is_duration = [ $initial, 0, [ $piece->[1] ], [ $length ] ];
710 4 50       17 if( $is_duration->[0] =~ /[hms]/ ){
711 4         9 $piece->[0] = '';
712 4         8 $piece->[1] = '';
713 4         7 $prior_duration = $is_duration->[0];
714             ###LogSD $phone->talk( level => 'debug', message => [
715             ###LogSD "found a duration piece:", $is_duration,
716             ###LogSD "with prior duration: $prior_duration" ] );
717             }else{
718 0         0 confess "Bad duration element found: $is_duration->[0]";
719             }
720             }elsif( ref( $is_duration ) eq 'ARRAY' ){
721             ###LogSD $phone->talk( level => 'debug', message =>[ "adding to duration", $piece ] );
722 8         16 my $next_duration = $duration_order->{$prior_duration};
723 8 50       104 if( $piece->[0] eq '.' ){
    50          
724 0         0 push @{$is_duration->[2]}, $piece->[0];
  0         0  
725             ###LogSD $phone->talk( level => 'debug', message => [
726             ###LogSD "found a period" ] );
727             }elsif( $piece->[0] =~ /$next_duration/ ){
728 8         10 my $length = length( $piece->[0] );
729 8         8 $is_duration->[1]++;
730 8 100       18 push @{$is_duration->[2]}, $piece->[1] if $piece->[1];
  4         13  
731 8         9 push @{$is_duration->[3]}, $length;
  8         11  
732 8         19 ($prior_duration,) = split //, $piece->[0];
733 8 50       19 if( $piece->[0] =~ /^0+$/ ){
734 0         0 $piece->[0] =~ s/0/S/g;
735 0         0 $sub_seconds = $piece->[0];
736             ###LogSD $phone->talk( level => 'debug', message => [
737             ###LogSD "found a subseconds format piece: $sub_seconds" ] );
738             }
739 8         9 $piece->[0] = '';
740 8         10 $piece->[1] = '';
741             ###LogSD $phone->talk( level => 'debug', message => [
742             ###LogSD "Current duration:", $is_duration,
743             ###LogSD "with prior duration: $prior_duration" ] );
744             }else{
745 0         0 confess "Bad duration element found: $piece->[0]";
746             }
747             }elsif( $piece->[0] =~ /m/ ){
748             ###LogSD $phone->talk( level => 'debug', message =>[ "Minutes or Months" ] );
749 60 100 100     424 if( ($cldr_string and $cldr_string =~ /:'?$/) or ($piece->[1] and $piece->[1] eq ':') ){
      100        
      66        
750             ###LogSD $phone->talk( level => 'debug', message => [
751             ###LogSD "Found minutes - leave them alone" ] );
752             }else{
753 32         92 $piece->[0] =~ s/m/L/g;
754             ###LogSD $phone->talk( level => 'debug', message => [
755             ###LogSD "Converting to cldr stand alone months (m->L)" ] );
756             }
757             }elsif( $piece->[0] =~ /h/ ){
758 20         58 $piece->[0] =~ s/h/H/g;
759             ###LogSD $phone->talk( level => 'debug', message => [
760             ###LogSD "Converting 12 hour clock to 24 hour clock" ] );
761             }elsif( $piece->[0] =~ /AM?\/PM?/i ){
762 8         20 $cldr_string =~ s/H/h/g;
763 8         14 $piece->[0] = 'a';
764             ###LogSD $phone->talk( level => 'debug', message =>[ "Set 12 hour clock and AM/PM" ] );
765             }elsif( $piece->[0] =~ /d{3,5}/ ){
766 4         17 $piece->[0] =~ s/d/E/g;
767             ###LogSD $phone->talk( level => 'debug', message =>[ "Found a weekday request" ] );
768             }elsif( !$sub_seconds and $piece->[0] =~ /[\.]/){#
769 4         6 $piece->[0] = "'.'";
770             #~ $piece->[0] = "':'";
771 4         7 $sub_seconds = 1;
772             ###LogSD $phone->talk( level => 'debug', message =>[ "Starting sub seconds" ] );
773             }elsif( $sub_seconds eq '1' ){
774             ###LogSD $phone->talk( level => 'debug', message =>[ "Formatting sub seconds" ] );
775 4 50       15 if( $piece->[0] =~ /^0+$/ ){
776 4         13 $piece->[0] =~ s/0/S/g;
777 4         6 $sub_seconds = $piece->[0];
778 4         6 $piece->[0] = '';
779             ###LogSD $phone->talk( level => 'debug', message => [
780             ###LogSD "found a subseconds format piece: $sub_seconds" ] );
781             }else{
782 0         0 confess "Bad sub-seconds element after [$cldr_string] found: $piece->[0]";
783             }
784             }
785 184 100 100     380 if( $sub_seconds and $sub_seconds ne '1' ){
786 4         5 $format_remainder .= $piece->[0];
787             }else{
788 180         191 $cldr_string .= $piece->[0];
789             }
790             }
791 192 100       306 if( $piece->[1] ){
792 108 50 33     209 if( $sub_seconds and $sub_seconds ne '1' ){
793 0         0 $format_remainder .= $piece->[1];
794             }else{
795 108         128 $cldr_string .= $piece->[1];
796             }
797             }
798             ###LogSD $phone->talk( level => 'debug', message => [
799             ###LogSD (($cldr_string) ? "Updated CLDR string: $cldr_string" : undef),
800             ###LogSD (($format_remainder) ? "Updated format remainder: $format_remainder" : undef),
801             ###LogSD (($is_duration) ? ('Duration ref:', $is_duration) : undef) ] );
802             }
803             ###LogSD $phone->talk( level => 'debug', message => [
804             ###LogSD "Updated CLDR string: $cldr_string",
805             ###LogSD (($is_duration) ? ('...and duration:', $is_duration) : undef ),
806             ###LogSD (($sub_seconds) ? ('...and sub seconds:' . $sub_seconds) : undef ),
807             ###LogSD (($format_remainder) ? ('...and format_remainder:' . $format_remainder) : undef ) ] );
808 60         258 return( $cldr_string, $is_duration, $sub_seconds, $format_remainder );
809             }
810              
811             sub _build_duration{
812 14     14   18 my( $self, $duration_ref, $delta_seconds, $delta_nanosecs ) = @_;
813             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
814             ###LogSD $self->get_all_space . '::hidden::_build_date::_build_duration', );
815             ###LogSD $phone->talk( level => 'debug', message => [
816             ###LogSD 'Building a duration string with duration ref:', $duration_ref,
817             ###LogSD "With delta seconds: $delta_seconds",
818             ###LogSD (($delta_nanosecs) ? "And delta nanoseconds: $delta_nanosecs" : undef) ] );
819 14         12 my $return_string;
820 14         18 my $key = $duration_ref->[0];
821 14         14 my $first = 1;
822 14         21 for my $position ( 0 .. $duration_ref->[1] ){
823 42 50       64 if( $key eq '0' ){
824 0         0 my $length = length( $last_sub_seconds );
825 0         0 $return_string .= '.' . sprintf( "%0.${length}f", $delta_nanosecs/1000000000);
826             ###LogSD $phone->talk( level => 'debug', message => [
827             ###LogSD "Return string with nanoseconds: $return_string", ] );
828             }
829 42 100       56 if( $key eq 's' ){
830 14 50       33 $return_string .= ( $first ) ? $delta_seconds :
831             sprintf "%0$duration_ref->[3]->[$position]d", $delta_seconds;
832 14         13 $first = 0;
833 14         17 $key = $duration_order->{$key};
834             ###LogSD $phone->talk( level => 'debug', message => [
835             ###LogSD "Delta seconds: $delta_seconds",
836             ###LogSD "Next key to process: $key" ] );
837             }
838 42 100       57 if( $key eq 'm' ){
839 14         17 my $minutes = int($delta_seconds/60);
840 14         14 $delta_seconds = $delta_seconds - ($minutes*60);
841 14 50       62 $return_string .= ( $first ) ? $minutes :
842             sprintf "%0$duration_ref->[3]->[$position]d", $minutes;
843 14         9 $first = 0;
844 14         17 $key = $duration_order->{$key};
845             ###LogSD $phone->talk( level => 'debug', message => [
846             ###LogSD "Calculated minutes: $minutes",
847             ###LogSD "Remaining seconds: $delta_seconds",
848             ###LogSD "Next key to process: $key" ] );
849             }
850 42 100       67 if( $key eq 'h' ){
851 14         22 my $hours = int($delta_seconds /(60*60));
852 14         17 $delta_seconds = $delta_seconds - ($hours*60*60);
853 14 50       30 $return_string .= ( $first ) ? $hours :
854             sprintf "%0$duration_ref->[3]->[$position]d", $hours;
855 14         13 $first = 0;
856 14         26 $key = $duration_order->{$key};
857             ###LogSD $phone->talk( level => 'debug', message => [
858             ###LogSD "Calculated hours: $hours",
859             ###LogSD "Remaining seconds: $delta_seconds",
860             ###LogSD "Next key to process: $key" ] );
861             }
862 42 100       73 $return_string .= $duration_ref->[2]->[$position] if $duration_ref->[2]->[$position];
863             }
864 14         107 return $return_string;
865             }
866              
867             sub _build_number{
868 88     88   148 my( $self, $type_filter, $list_ref ) = @_;
869             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
870             ###LogSD $self->get_all_space . '::hidden::_build_number', );
871             ###LogSD $phone->talk( level => 'debug', message => [
872             ###LogSD "Processing a number list to see how it should be converted",
873             ###LogSD 'With type constraint: ' . $type_filter->name,
874             ###LogSD '..using list ref:' , $list_ref ] );
875 88         113 my ( $code_hash_ref, $number_type, );
876              
877             # Resolve zero replacements quickly
878 88 0 33     331 if( $type_filter->name eq 'ZeroOrUndef' and
      33        
879             !$list_ref->[-1]->[0] and $list_ref->[-1]->[1] eq '"-"' ){
880             ###LogSD $phone->talk( level => 'debug', message =>[
881             ###LogSD "Found a zero to bar replacement" ] );
882 0         0 my $return_string;
883 0         0 for my $piece ( @$list_ref ){
884 0         0 $return_string .= $piece->[1];
885             }
886 0         0 $return_string =~ s/"\-"/\-/;
887 0     0   0 return( 'NUMBER', $type_filter, sub{ $return_string } );
  0         0  
888             }
889              
890             # Handle trailing negative
891 88 100       727 if( $list_ref->[-1]->[0] =~ /(.*[\d\#])\-$/ ){
892             ###LogSD $phone->talk( level => 'debug', message =>[ "Found a trailing negative sign", ] );
893 1         4 $list_ref->[-1]->[0] = $1;
894 1         2 push @$list_ref, [ undef, '-' ];
895             }
896              
897             # Process once to determine what to do
898 88         151 for my $piece ( @$list_ref ){
899             ###LogSD $phone->talk( level => 'debug', message => [
900             ###LogSD "processing number piece:", $piece ] );
901 273 100       454 if( defined $piece->[0] ){
902 216 100       1145 if( my @result = $piece->[0] =~ /^([0-9#\?]+)([,\-\_])?([#0\?]+)?(,+)?$/ ){
    50          
903             ###LogSD $phone->talk( level => 'debug', message => [
904             ###LogSD "Regex yielded result:", @result ] );
905 170 100       597 my $comma = ($2) ? $2 : undef,
    100          
906             my $comma_less = defined( $3) ? "$1$3" : $1;
907 170 100       286 my $comma_group = $3 ? length( $3 ) : 0;
908 170 0 0     252 my $divide_by_thousands = ( $4 ) ? (( $2 and $2 ne ',' ) ? $4 : "$2$4" ) : undef;#eval{ $2 . $4 }
    50          
909 170 100       506 my $divisor = $1 if $1 =~ /^([0-9]+)$/;
910 170         138 my ( $leading_zeros, $trailinq_zeros );
911 170 100       442 if( $comma_less =~ /^[\#\?]*(0+)$/ ){
912 110         131 $leading_zeros = $1;
913             }
914 170 100       337 if( $comma_less =~ /^(0+)[\#\?]*$/ ){
915 52         64 $trailinq_zeros = $1;
916             }
917 170 50       247 $code_hash_ref->{divide_by_thousands} = length( $divide_by_thousands ) if $divide_by_thousands;
918             ###LogSD $phone->talk( level => 'debug', message => [
919             ###LogSD "The comma less string is extracted to: $comma_less",
920             ###LogSD ((defined $comma_group) ? "The separator group length is: $comma_group" : undef),
921             ###LogSD (($comma) ? "The separator character is: $comma" : undef),
922             ###LogSD (($leading_zeros and length( $leading_zeros )) ? ".. w/leading zeros: $leading_zeros" : undef),
923             ###LogSD (($trailinq_zeros and length( $trailinq_zeros )) ? ".. w/trailing zeros: $trailinq_zeros" : undef),
924             ###LogSD (($divisor) ? "..with identified divisor: $divisor" : undef),
925             ###LogSD 'Initial code hash:', $code_hash_ref] );
926 170 100 100     574 if( !$number_type ){
    100 66        
    50          
927 88         109 $number_type = 'INTEGER';
928 88 50 33     221 $code_hash_ref->{integer}->{leading_zeros} = length( $leading_zeros ) if $leading_zeros and length( $leading_zeros );
929 88         218 $code_hash_ref->{integer}->{minimum_length} = length( $comma_less );
930 88 100       160 if( $comma ){
931 56         58 @{$code_hash_ref->{integer}}{ 'group_length', 'comma' } = ( $comma_group, $comma );
  56         130  
932             }
933 88 100       276 if( defined $piece->[1] ){
934 32 100       159 if( $piece->[1] =~ /(\s+)/ ){
    50          
935 20         76 $code_hash_ref->{separator} = $1;
936             }elsif( $piece->[1] eq '/' ){
937 0         0 $number_type = 'FRACTION';
938 0 0 0     0 $code_hash_ref->{numerator}->{leading_zeros} = length( $leading_zeros ) if $leading_zeros and length( $leading_zeros );
939 0         0 delete $code_hash_ref->{integer};
940             }
941             }
942             }elsif( ($number_type eq 'INTEGER') or $number_type eq 'DECIMAL' ){
943 56 100 100     224 if( $piece->[1] and $piece->[1] eq '/'){
944 20         49 $number_type = 'FRACTION';
945             }else{
946 36         41 $number_type = 'DECIMAL';
947 36 100 66     191 $code_hash_ref->{decimal}->{trailing_zeros} = length( $trailinq_zeros ) if $trailinq_zeros and length( $trailinq_zeros );
948 36         111 $code_hash_ref->{decimal}->{max_length} = length( $comma_less );
949             }
950             }elsif( ($number_type eq 'SCIENTIFIC') or $number_type eq 'FRACTION' ){
951 26 100 66     76 $code_hash_ref->{exponent}->{leading_zeros} = length( $leading_zeros ) if $leading_zeros and length( $leading_zeros );
952 26         81 $code_hash_ref->{fraction}->{target_length} = length( $comma_less );
953 26 100       68 if( $divisor ){
954 14         44 $code_hash_ref->{fraction}->{divisor} = $divisor;
955             }
956             }
957             ###LogSD $phone->talk( level => 'trace', message => [
958             ###LogSD "Current number type: $number_type", 'updated settings:', $code_hash_ref] );
959             }elsif( $piece->[0] =~ /^((\.)|([Ee][+\-])|(%))$/ ){
960 46 100       102 if( $2 ){
    100          
961 36         51 $number_type = 'DECIMAL';
962 36         113 $code_hash_ref->{separator} = $1;
963             }elsif( $3 ){
964 6         8 $number_type = 'SCIENTIFIC';
965 6         16 $code_hash_ref->{separator} = $2;
966             }else{
967 4         9 $number_type = 'PERCENT';
968             }
969             ###LogSD $phone->talk( level => 'info', message => [
970             ###LogSD "Number type now: $number_type" ] );
971             }else{
972 0         0 confess "badly formed number format passed: $piece->[0]";
973             }
974             }
975             }
976              
977             # Set negative type
978 88 100       209 if( $type_filter->name eq 'NegativeNum' ){
979             ###LogSD $phone->talk( level => 'info', message => [
980             ###LogSD "Setting this as a negative number type" ] );
981 25         102 $code_hash_ref->{negative_type} = 1;
982             }
983              
984 88         406 my $method = '_build_' . lc( $number_type ) . '_sub';
985             ###LogSD $phone->talk( level => 'trace', message => [
986             ###LogSD "Resolved the number type to: $number_type",
987             ###LogSD 'Working with settings:', $list_ref, $code_hash_ref ] );
988 88         264 my $conversion_sub = $self->$method( $type_filter, $list_ref, $code_hash_ref );
989              
990 88         242 return( $number_type, $type_filter, $conversion_sub );
991             }
992              
993             sub _build_integer_sub{
994 28     28   37 my( $self, $type_filter, $list_ref, $conversion_defs ) = @_;
995             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
996             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_integer_sub', );
997             ###LogSD $phone->talk( level => 'debug', message => [
998             ###LogSD "Building an anonymous sub to return integer values",
999             ###LogSD 'With type constraint: ' . $type_filter->name,
1000             ###LogSD '..using list ref:' , $list_ref, '..and conversion defs:', $conversion_defs ] );
1001              
1002 28         26 my $sprintf_string;
1003             # Process once to determine what to do
1004 28         27 my $found_integer = 0;
1005 28         41 for my $piece ( @$list_ref ){
1006             ###LogSD $phone->talk( level => 'debug', message => [
1007             ###LogSD "processing number piece:", $piece ] );
1008 56 100 66     205 if( !$found_integer and defined $piece->[0] ){
1009 28         28 $sprintf_string .= '%s';
1010 28         28 $found_integer = 1;
1011             }
1012 56 100       82 if( $piece->[1] ){
1013 36         44 $sprintf_string .= $piece->[1];
1014             }
1015             }
1016 28         34 $conversion_defs->{no_decimal} = 1;
1017 28         41 $conversion_defs->{sprintf_string} = $sprintf_string;
1018             ###LogSD $phone->talk( level => 'debug', message => [
1019             ###LogSD "Final sprintf string: $sprintf_string" ] );
1020 28         33 my $dispatch_sequence = $number_build_dispatch->{decimal};
1021              
1022             my $conversion_sub = sub{
1023             #~ print "Using Integer sub!!!!!!\n";
1024             ###LogSD my $sub_phone = Log::Shiras::Telephone->new( name_space =>
1025             ###LogSD $self->get_log_space . '::Cell::_hidden::_return_value_only::_build_number::_build_integer_sub', );
1026 112     112   14659 my $adjusted_input = $_[0];
1027 112 50 33     519 if( !defined $adjusted_input or $adjusted_input eq '' ){
1028             ###LogSD $sub_phone->talk( level => 'debug', message => [
1029             ###LogSD "Return undef for empty strings" ] );
1030 0         0 return undef;
1031             }
1032 112         1275 my $value_definitions = clone( $conversion_defs );
1033 112         187 $value_definitions->{initial_value} = $adjusted_input;
1034             ###LogSD $sub_phone->talk( level => 'trace', message => [
1035             ###LogSD 'Building scientific output with:', $conversion_defs,
1036             ###LogSD '..and dispatch sequence:', $dispatch_sequence ] );
1037 112         224 my $built_ref = $self->_build_elements( $dispatch_sequence, $value_definitions );
1038             ###LogSD $sub_phone->talk( level => 'trace', message => [
1039             ###LogSD "Received built ref:", $built_ref ] );
1040             my $return .= sprintf(
1041             $built_ref->{sprintf_string},
1042             $built_ref->{integer}->{value}
1043 112         269 );
1044 112 100 66     253 $return = $built_ref->{sign} . $return if $built_ref->{sign} and $return;
1045 112         412 return $return;
1046 28         127 };
1047             ###LogSD $phone->talk( level => 'debug', message => [
1048             ###LogSD "Conversion sub for filter name: " . $type_filter->name, $conversion_sub ] );
1049              
1050 28         45 return $conversion_sub;
1051             }
1052              
1053             sub _build_decimal_sub{
1054 30     30   37 my( $self, $type_filter, $list_ref, $conversion_defs ) = @_;
1055             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1056             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_decimal_sub', );
1057             ###LogSD $phone->talk( level => 'debug', message => [
1058             ###LogSD "Building an anonymous sub to return decimal values",
1059             ###LogSD 'With type constraint: ' . $type_filter->name,
1060             ###LogSD '..using list ref:' , $list_ref, '..and code hash ref:', $conversion_defs ] );
1061              
1062 30         29 my $sprintf_string;
1063             # Process once to determine what to do
1064 30         41 for my $piece ( @$list_ref ){
1065             ###LogSD $phone->talk( level => 'debug', message => [
1066             ###LogSD "processing number piece:", $piece ] );
1067 119 100       167 if( defined $piece->[0] ){
1068 90 100       111 if( $piece->[0] eq '.' ){
1069 30         26 $sprintf_string .= '.';
1070             }else{
1071 60         54 $sprintf_string .= '%s';
1072             }
1073             }
1074 119 100       155 if( $piece->[1] ){
1075 37         40 $sprintf_string .= $piece->[1];
1076             }
1077             }
1078 30         41 $conversion_defs->{sprintf_string} = $sprintf_string;
1079             ###LogSD $phone->talk( level => 'debug', message => [
1080             ###LogSD "Final sprintf string: $sprintf_string" ] );
1081 30         42 my $dispatch_sequence = $number_build_dispatch->{decimal};
1082              
1083             my $conversion_sub = sub{
1084             #~ print "Using Decimal sub!!!!!!\n";
1085             ###LogSD my $sub_phone = Log::Shiras::Telephone->new( name_space =>
1086             ###LogSD $self->get_log_space . '::Cell::_hidden::_return_value_only::_build_number::_build_decimal_sub', );
1087 114     114   14921 my $adjusted_input = $_[0];
1088 114 50 33     522 if( !defined $adjusted_input or $adjusted_input eq '' ){
1089             ###LogSD $phone->talk( level => 'debug', message => [
1090             ###LogSD "Return undef for empty strings" ] );
1091 0         0 return undef;
1092             }
1093 114         1616 my $value_definitions = clone( $conversion_defs );
1094 114         223 $value_definitions->{initial_value} = $adjusted_input;
1095             ###LogSD $sub_phone->talk( level => 'trace', message => [
1096             ###LogSD 'Building scientific output with:', $conversion_defs,
1097             ###LogSD '..and dispatch sequence:', $dispatch_sequence ] );
1098 114         250 my $built_ref = $self->_build_elements( $dispatch_sequence, $value_definitions );
1099             ###LogSD $sub_phone->talk( level => 'trace', message => [
1100             ###LogSD "Received built ref:", $built_ref ] );
1101             my $return .= sprintf(
1102             $built_ref->{sprintf_string},
1103             $built_ref->{integer}->{value},
1104             $built_ref->{decimal}->{value},
1105 114         343 );
1106 114 100 66     227 $return = $built_ref->{sign} . $return if $built_ref->{sign} and $return;
1107 114         486 return $return;
1108 30         137 };
1109             ###LogSD $phone->talk( level => 'debug', message => [
1110             ###LogSD "Conversion sub for filter name: " . $type_filter->name, $conversion_sub ] );
1111              
1112 30         70 return $conversion_sub;
1113             }
1114              
1115             sub _build_percent_sub{
1116 4     4   7 my( $self, $type_filter, $list_ref, $conversion_defs ) = @_;
1117             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1118             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_percent_sub', );
1119             ###LogSD $phone->talk( level => 'debug', message => [
1120             ###LogSD "Building an anonymous sub to return decimal values",
1121             ###LogSD 'With type constraint: ' . $type_filter->name,
1122             ###LogSD '..using list ref:' , $list_ref, '..and code hash ref:', $conversion_defs ] );
1123              
1124 4         5 my $sprintf_string;
1125 4         6 my $decimal_count = 0;
1126             # Process once to determine what to do
1127 4         8 for my $piece ( @$list_ref ){
1128             ###LogSD $phone->talk( level => 'debug', message => [
1129             ###LogSD "processing number piece:", $piece ] );
1130 12 50       21 if( defined $piece->[0] ){
1131 12 100       25 if( $piece->[0] eq '%' ){
    100          
1132 4         5 $sprintf_string .= '%%';
1133             }elsif( $piece->[0] eq '.' ){
1134 2         4 $sprintf_string .= '.';
1135             }else{
1136 6         10 $sprintf_string .= '%s';
1137 6         7 $decimal_count++;
1138             }
1139             }
1140 12 50       17 if( $piece->[1] ){
1141 0         0 $sprintf_string .= $piece->[1];
1142             }
1143             }
1144 4 100       14 $conversion_defs->{no_decimal} = 1 if $decimal_count < 2;
1145 4         6 $conversion_defs->{sprintf_string} = $sprintf_string;
1146             ###LogSD $phone->talk( level => 'debug', message => [
1147             ###LogSD "Final sprintf string: $sprintf_string" ] );
1148 4         7 my $dispatch_sequence = $number_build_dispatch->{percent};
1149              
1150             my $conversion_sub = sub{
1151             #~ print "Using Percent sub!!!!!!\n";
1152             ###LogSD my $sub_phone = Log::Shiras::Telephone->new( name_space =>
1153             ###LogSD $self->get_log_space . '::Cell::_hidden::_return_value_only::_build_number::_build_percent_sub', );
1154 28     28   22869 my $adjusted_input = $_[0];
1155 28 50 33     155 if( !defined $adjusted_input or $adjusted_input eq '' ){
1156             ###LogSD $sub_phone->talk( level => 'debug', message => [
1157             ###LogSD "Return undef for empty strings" ] );
1158 0         0 return undef;
1159             }
1160 28         315 my $value_definitions = clone( $conversion_defs );
1161 28         49 $value_definitions->{initial_value} = $adjusted_input;
1162             ###LogSD $sub_phone->talk( level => 'trace', message => [
1163             ###LogSD 'Building scientific output with:', $conversion_defs,
1164             ###LogSD '..and dispatch sequence:', $dispatch_sequence ] );
1165 28         59 my $built_ref = $self->_build_elements( $dispatch_sequence, $value_definitions );
1166             ###LogSD $sub_phone->talk( level => 'trace', message => [
1167             ###LogSD "Received built ref:", $built_ref ] );
1168 28         30 my $return;
1169 28 100       40 if( $decimal_count < 2 ){
1170             $return .= sprintf(
1171             $built_ref->{sprintf_string},
1172             $built_ref->{integer}->{value},
1173 14         57 );
1174             }else{
1175             $return .= sprintf(
1176             $built_ref->{sprintf_string},
1177             $built_ref->{integer}->{value},
1178             $built_ref->{decimal}->{value},
1179 14         93 );
1180             }
1181 28 100 66     74 $return = $built_ref->{sign} . $return if $built_ref->{sign} and $return;
1182 28         115 return $return;
1183 4         20 };
1184             ###LogSD $phone->talk( level => 'debug', message => [
1185             ###LogSD "Conversion sub for filter name: " . $type_filter->name, $conversion_sub ] );
1186              
1187 4         5 return $conversion_sub;
1188             }
1189              
1190             sub _build_scientific_sub{
1191 6     6   13 my( $self, $type_filter, $list_ref, $conversion_defs ) = @_;
1192             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1193             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_scientific_sub', );
1194             ###LogSD $phone->talk( level => 'debug', message => [
1195             ###LogSD "Building an anonymous sub to return scientific values",
1196             ###LogSD 'With type constraint: ' . $type_filter->name,
1197             ###LogSD '..using list ref:' , $list_ref, '..and code hash ref:', $conversion_defs ] );
1198              
1199             # Process once to determine what to do
1200 6         6 my ( $sprintf_string, $exponent_sprintf );
1201 6 100       25 $conversion_defs->{no_decimal} = ( exists $conversion_defs->{decimal} ) ? 0 : 1 ;
1202 6         16 for my $piece ( @$list_ref ){
1203             ###LogSD $phone->talk( level => 'debug', message => [
1204             ###LogSD "processing number piece:", $piece ] );
1205 26 50       40 if( defined $piece->[0] ){
1206 26 100       81 if( $piece->[0] =~ /(E)(.)/ ){
    100          
    100          
1207 6         12 $sprintf_string .= $1;
1208 6         12 $exponent_sprintf = '%';
1209 6 50       18 $exponent_sprintf .= '+' if $2 eq '+';
1210 6 100       23 if( exists $conversion_defs->{exponent}->{leading_zeros} ){
1211 2         5 $exponent_sprintf .= '0.' . $conversion_defs->{exponent}->{leading_zeros};
1212             }
1213 6         8 $exponent_sprintf .= 'd';
1214             }elsif( $piece->[0] eq '.' ){
1215 4         6 $sprintf_string .= '.';
1216 4         8 $conversion_defs->{no_decimal} = 0;
1217             }elsif( $exponent_sprintf ){
1218 6         10 $sprintf_string .= $exponent_sprintf;
1219             }else{
1220 10         11 $sprintf_string .= '%s';
1221             }
1222             }
1223 26 50       42 if( $piece->[1] ){
1224 0         0 $sprintf_string .= $piece->[1];
1225             }
1226             }
1227 6         14 $conversion_defs->{sprintf_string} = $sprintf_string;
1228             ###LogSD $phone->talk( level => 'debug', message => [
1229             ###LogSD "Final sprintf string: $sprintf_string" ] );
1230 6         15 my $dispatch_sequence = $number_build_dispatch->{scientific};
1231              
1232             my $conversion_sub = sub{
1233 48     48   28927 my $adjusted_input = $_[0];
1234 48 50 33     524 if( !defined $adjusted_input or $adjusted_input eq '' ){
    50 0        
      0        
      0        
      33        
1235             ###LogSD $phone->talk( level => 'debug', message => [
1236             ###LogSD "Return undef for empty strings" ] );
1237 0         0 return undef;
1238             }elsif( $adjusted_input =~ /^\-?\d*(\.\d+)?$/ or
1239             ( $adjusted_input =~ /^(\-)?((\d{1,3})?(\.\d+)?)[Ee](\-)?(\d+)$/ and $2 and $6 and $6 < 309 ) ){# Check for non-scientific numbers passed to scientific format
1240             ###LogSD $phone->talk( level => 'trace', message => [
1241             ###LogSD "Passed the first scientific format test with: $adjusted_input" ] );
1242 48         875 my $value_definitions = clone( $conversion_defs );
1243 48         111 $value_definitions->{initial_value} = $adjusted_input;
1244              
1245             ###LogSD $phone->talk( level => 'trace', message => [
1246             ###LogSD 'Building scientific output with:', $conversion_defs,
1247             ###LogSD '..and dispatch sequence:', $dispatch_sequence ] );
1248 48         115 my $built_ref = $self->_build_elements( $dispatch_sequence, $value_definitions );
1249             ###LogSD $phone->talk( level => 'trace', message => [
1250             ###LogSD "Received built ref:", $built_ref ] );
1251 48         40 my $return;
1252 48 100       70 if( $built_ref->{no_decimal} ){
1253             $return .= sprintf(
1254             $built_ref->{sprintf_string},
1255             $built_ref->{integer}->{value},
1256             $built_ref->{exponent}->{value}
1257 16         55 );
1258             }else{
1259             $return .= sprintf(
1260             $built_ref->{sprintf_string},
1261             $built_ref->{integer}->{value},
1262             $built_ref->{decimal}->{value} ,
1263             $built_ref->{exponent}->{value}
1264 32         110 );
1265             }
1266 48 100 66     165 $return = $built_ref->{sign} . $return if $built_ref->{sign} and $return;
1267 48         274 return $return;
1268             }else{
1269             ###LogSD $phone->talk( level => 'trace', message => [
1270             ###LogSD "Doesn't really seem like this is a scientific number recognized by excel: $adjusted_input" ] );
1271 0         0 return $adjusted_input;
1272             }
1273 6         34 };
1274             ###LogSD $phone->talk( level => 'debug', message => [
1275             ###LogSD "Conversion sub for filter name: " . $type_filter->name, $conversion_sub ] );
1276              
1277 6         12 return $conversion_sub;
1278             }
1279              
1280             sub _build_fraction_sub{
1281 20     20   44 my( $self, $type_filter, $list_ref, $conversion_defs ) = @_;
1282             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1283             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_fraction_sub', );
1284             ###LogSD $phone->talk( level => 'debug', message => [
1285             ###LogSD "Building an anonymous sub to return integer and fraction strings",
1286             ###LogSD 'With type constraint: ' . $type_filter->name,
1287             ###LogSD '..using list ref:' , $list_ref, '..and code hash ref:', $conversion_defs ] );
1288              
1289             # I'm worried about pulling the sprintf parser out of here and I may need to put it back sometime
1290              
1291 20         52 my $dispatch_sequence = $number_build_dispatch->{fraction};
1292             my $conversion_sub = sub{
1293             #~ print "Using Fraction sub!!!!!!\n";
1294             ###LogSD my $sub_phone = Log::Shiras::Telephone->new( name_space =>
1295             ###LogSD $self->get_log_space . '::Cell::_hidden::_return_value_only::_build_number::_build_fraction_sub', );
1296 460     460   257056 my $adjusted_input = $_[0];
1297 460 50 33     2201 if( !defined $adjusted_input or $adjusted_input eq '' ){
1298             ###LogSD $sub_phone->talk( level => 'debug', message => [
1299             ###LogSD "Return undef for empty strings" ] );
1300 0         0 return undef;
1301             }
1302 460         5173 my $value_definitions = clone( $conversion_defs );
1303 460         880 $value_definitions->{initial_value} = $adjusted_input;
1304             ###LogSD $sub_phone->talk( level => 'trace', message => [
1305             ###LogSD 'Building scientific output with:', $conversion_defs,
1306             ###LogSD '..and dispatch sequence:', $dispatch_sequence ] );
1307 460         950 my $built_ref = $self->_build_elements( $dispatch_sequence, $value_definitions );
1308             ###LogSD $sub_phone->talk( level => 'trace', message => [
1309             ###LogSD "Received built ref:", $built_ref ] );
1310 460         387 my $return;
1311 460 100       710 if( $built_ref->{integer}->{value} ){
1312 298         649 $return = sprintf( '%s', $built_ref->{integer}->{value} );
1313 298 100       510 if( $built_ref->{fraction}->{value} ){
1314 234         273 $return .= ' ';
1315             }
1316             }
1317 460 100       648 if( $built_ref->{fraction}->{value} ){
1318 332         338 $return .= $built_ref->{fraction}->{value};
1319             }
1320 460 50 66     768 if( !$return and $built_ref->{initial_value} ){
1321 64         71 $return = 0;
1322             }
1323 460 100 100     1248 $return = $built_ref->{sign} . $return if $built_ref->{sign} and $return;
1324 460         1997 return $return;
1325 20         130 };
1326             ###LogSD $phone->talk( level => 'debug', message => [
1327             ###LogSD "Conversion sub for filter name: " . $type_filter->name, $conversion_sub ] );
1328              
1329 20         48 return $conversion_sub;
1330             }
1331              
1332             sub _build_elements{
1333 762     762   863 my( $self, $dispatch_ref, $value_definitions, ) = @_;
1334             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1335             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements', );
1336             ###LogSD $phone->talk( level => 'debug', message => [
1337             ###LogSD 'Reached the dispatcher for number building with:', $value_definitions,
1338             ###LogSD '..using dispatch list', $dispatch_ref ] );
1339 762         1037 for my $method ( @$dispatch_ref ){
1340 3398         6013 $value_definitions = $self->$method( $value_definitions );
1341             ###LogSD $phone->talk( level => 'debug', message => [
1342             ###LogSD 'Updated value definitions:', $value_definitions, ] );
1343             }
1344 762         732 return $value_definitions;
1345             }
1346              
1347             sub _convert_negative{
1348 762     762   694 my( $self, $value_definitions, ) = @_;
1349             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1350             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements::_convert_negative', );
1351             ###LogSD $phone->talk( level => 'debug', message => [
1352             ###LogSD 'Reached _convert_negative with:', $value_definitions, ] );
1353              
1354 762 100 66     1699 if( $value_definitions->{negative_type} and $value_definitions->{initial_value} < 0 ){
1355 73         118 $value_definitions->{initial_value} = $value_definitions->{initial_value} * -1;
1356             }
1357             ###LogSD $phone->talk( level => 'debug', message => [
1358             ###LogSD 'updated value definitions:', $value_definitions, ] );
1359 762         998 return $value_definitions;
1360             }
1361              
1362             sub _divide_by_thousands{
1363 226     226   204 my( $self, $value_definitions, ) = @_;
1364             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1365             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements::_divide_by_thousands', );
1366             ###LogSD $phone->talk( level => 'debug', message => [
1367             ###LogSD 'Reached _convert_to_percent with:', $value_definitions, ] );
1368 226 50 33     600 if( $value_definitions->{initial_value} and
1369             $value_definitions->{divide_by_thousands} ){
1370             $value_definitions->{initial_value} =
1371             $value_definitions->{initial_value}/
1372 0         0 ( 1000**$value_definitions->{divide_by_thousands} );
1373             }
1374             ###LogSD $phone->talk( level => 'debug', message => [
1375             ###LogSD 'updated value definitions:', $value_definitions, ] );
1376 226         239 return $value_definitions;
1377             }
1378              
1379             sub _convert_to_percent{
1380 28     28   31 my( $self, $value_definitions, ) = @_;
1381             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1382             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements::_convert_to_percent', );
1383             ###LogSD $phone->talk( level => 'debug', message => [
1384             ###LogSD 'Reached _convert_to_percent with:', $value_definitions, ] );
1385              
1386 28         82 $value_definitions->{initial_value} = $value_definitions->{initial_value} * 100;
1387             ###LogSD $phone->talk( level => 'debug', message => [
1388             ###LogSD 'updated value definitions:', $value_definitions, ] );
1389 28         32 return $value_definitions;
1390             }
1391              
1392             sub _split_decimal_integer{
1393 762     762   601 my( $self, $value_definitions, ) = @_;
1394             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1395             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements::_split_decimal_integer', );
1396             ###LogSD $phone->talk( level => 'debug', message => [
1397             ###LogSD 'Reached _split_decimal_integer with:', $value_definitions, ] );
1398              
1399             # Extract negative sign
1400 762 100       2075 if( $value_definitions->{initial_value} < 0 ){
1401 272         339 $value_definitions->{sign} = '-';
1402 272         380 $value_definitions->{initial_value} = $value_definitions->{initial_value} * -1;
1403             }
1404              
1405             # Build the integer
1406 762         1019 $value_definitions->{integer}->{value} = int( $value_definitions->{initial_value} );
1407              
1408             # Build the decimal
1409 762         1599 $value_definitions->{decimal}->{value} = $value_definitions->{initial_value} - $value_definitions->{integer}->{value};
1410             ###LogSD $phone->talk( level => 'debug', message =>[ 'Updated ref: ', $value_definitions ] );
1411 762         990 return $value_definitions;
1412             }
1413              
1414             sub _move_decimal_point{
1415 48     48   46 my( $self, $value_definitions, ) = @_;
1416             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1417             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements::_move_decimal_point', );
1418             ###LogSD $phone->talk( level => 'debug', message => [
1419             ###LogSD 'Reached _move_decimal_point with:', $value_definitions, ] );
1420 48         67 my ( $exponent, $stopped );
1421 48 100 66     389 if(defined $value_definitions->{integer}->{value} and
    50          
1422             sprintf( '%.0f', $value_definitions->{integer}->{value} ) =~ /([1-9])/ ){
1423 36         102 $stopped = $+[0];
1424             ###LogSD $phone->talk( level => 'debug', message =>[ "Matched integer value at: $stopped", ] );
1425 36         180 $exponent = length( sprintf( '%.0f', $value_definitions->{integer}->{value} ) ) - $stopped;
1426             }elsif( $value_definitions->{decimal}->{value} ){
1427 12 50       120 if( $value_definitions->{decimal}->{value} =~ /E(-?\d+)$/i ){
    0          
1428 12         35 $exponent = $1 * 1;
1429             }elsif( $value_definitions->{decimal}->{value} =~ /([1-9])/ ){
1430 0         0 $exponent = $+[0] * -1;
1431 0         0 $exponent += 2;
1432             ###LogSD $phone->talk( level => 'debug', message =>[ "Matched decimal value at: $exponent", ] );
1433             }
1434             }else{
1435 0         0 $exponent = 0;
1436             }
1437             ###LogSD $phone->talk( level => 'debug', message =>[ "Initial exponent: $exponent", ] );
1438 48         69 my $exponent_remainder = $exponent % $value_definitions->{integer}->{minimum_length};
1439             ###LogSD $phone->talk( level => 'debug', message =>[ "Exponent remainder: $exponent_remainder", ] );
1440 48         46 $exponent -= $exponent_remainder;
1441             ###LogSD $phone->talk( level => 'debug', message =>[ "New exponent: $exponent", ] );
1442 48         56 $value_definitions->{exponent}->{value} = $exponent;
1443 48 100       110 if( $exponent < 0 ){
    100          
1444 12         34 my $adjustment = '1' . (0 x abs($exponent));
1445             ###LogSD $phone->talk( level => 'info', message => [
1446             ###LogSD "The exponent |$exponent| is less than zero - the decimal must move to the right by: $adjustment" ] );
1447 12         23 my $new_integer = $value_definitions->{integer}->{value} * $adjustment;
1448 12         22 my $new_decimal = $value_definitions->{decimal}->{value} * $adjustment;
1449 12         18 my $decimal_int = int( $new_decimal );
1450             ###LogSD $phone->talk( level => 'info', message => [
1451             ###LogSD "Bumped integer: $new_integer", "Bumped decimal: $new_decimal", "Decimal integer: $decimal_int" ] );
1452 12         18 $value_definitions->{integer}->{value} = $new_integer + $decimal_int;
1453 12         21 $value_definitions->{decimal}->{value} = $new_decimal - $decimal_int;
1454             }elsif( $exponent > 0 ){
1455 22         56 my $adjustment = '1' . (0 x $exponent);
1456             ###LogSD $phone->talk( level => 'info', message => [
1457             ###LogSD "The exponent -$exponent- is greater than zero - the decimal must move to the left" ] );
1458 22         49 my $new_integer = $value_definitions->{integer}->{value} / $adjustment;
1459 22         30 my $new_decimal = $value_definitions->{decimal}->{value} / $adjustment;
1460 22         28 my $integer_int = int( $new_integer );
1461 22         27 $value_definitions->{integer}->{value} = $integer_int;
1462 22         42 $value_definitions->{decimal}->{value} = $new_decimal + ($new_integer - $integer_int);
1463             }
1464              
1465             ###LogSD $phone->talk( level => 'debug', message => [
1466             ###LogSD 'Updated ref:', $value_definitions ] );
1467 48         85 return $value_definitions;
1468             }
1469              
1470             sub _round_decimal{
1471 302     302   373 my( $self, $value_definitions, ) = @_;
1472             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1473             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements::_round_decimal', );
1474             ###LogSD $phone->talk( level => 'debug', message => [
1475             ###LogSD 'Reached _round_decimal with:', $value_definitions, ] );
1476 302 100       611 if( $value_definitions->{no_decimal} ){
    50          
1477 142 100       275 if( $value_definitions->{decimal}->{value} > 0.4998 ){# Err on the side of fixing precision up
1478             ###LogSD $phone->talk( level => 'info', message => [
1479             ###LogSD 'Rouding the integer -' . $value_definitions->{integer}->{value} .
1480             ###LogSD "- for the no-decimal condition with decimal: $value_definitions->{decimal}->{value}", ] );
1481 34         45 $value_definitions->{integer}->{value}++;
1482             }
1483 142         241 delete $value_definitions->{decimal};
1484             }elsif( $value_definitions->{decimal}->{max_length} ){
1485             ###LogSD $phone->talk( level => 'info', message => [
1486             ###LogSD "Enforcing decimal max length: " . $value_definitions->{decimal}->{max_length} ] );
1487 160 100       245 if( $value_definitions->{decimal}->{value} ){
1488 70         179 my $adder = '0.' . (0 x $value_definitions->{decimal}->{max_length}) . '00002';
1489 70         118 my $sprintf_string = '%.' . $value_definitions->{decimal}->{max_length} . 'f';
1490 70         630 my $round_decimal = sprintf( $sprintf_string, ($value_definitions->{decimal}->{value}+$adder) );
1491             ###LogSD $phone->talk( level => 'info', message => [
1492             ###LogSD "Sprintf string: $sprintf_string", "Rounded decimal: $round_decimal", "Adder: $adder",] );
1493 70 50       172 if( $round_decimal >= 1 ){
1494 0         0 $value_definitions->{integer}->{value}++;
1495 0         0 $round_decimal -= 1;
1496             ###LogSD $phone->talk( level => 'info', message => [
1497             ###LogSD "New integer: " . $value_definitions->{integer}->{value}, "New decimal: $round_decimal" ] );
1498             }
1499 70         111 my $decimal_multiply = '1' . (0 x $value_definitions->{decimal}->{max_length});
1500 70         85 my $string_sprintf = '%0' . $value_definitions->{decimal}->{max_length} . 's';
1501 70         340 $value_definitions->{decimal}->{value} = sprintf( $string_sprintf, ($round_decimal * $decimal_multiply) );
1502             }
1503              
1504 160 100       273 if( !$value_definitions->{decimal}->{value} ){
1505 92         187 $value_definitions->{decimal}->{value} = 0 x $value_definitions->{decimal}->{max_length};
1506             }
1507             }
1508              
1509             ###LogSD $phone->talk( level => 'debug', message => [
1510             ###LogSD 'Updated ref:', $value_definitions ] );
1511 302         390 return $value_definitions;
1512             }
1513              
1514             sub _add_commas{
1515 762     762   753 my( $self, $value_definitions, ) = @_;
1516             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1517             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements::_add_commas', );
1518             ###LogSD $phone->talk( level => 'debug', message => [
1519             ###LogSD 'Reached _add_commas with:', $value_definitions, ] );
1520 762 100       1267 if( exists $value_definitions->{integer}->{comma} ){
1521             $value_definitions->{integer}->{value} = $self->_add_integer_separator(
1522             sprintf( '%.0f', $value_definitions->{integer}->{value} ),
1523             $value_definitions->{integer}->{comma},
1524             $value_definitions->{integer}->{group_length},
1525 214         740 );
1526             }
1527              
1528             ###LogSD $phone->talk( level => 'debug', message => [
1529             ###LogSD 'Updated ref:', $value_definitions ] );
1530 762         967 return $value_definitions;
1531             }
1532              
1533             sub _pad_exponent{
1534 48     48   45 my( $self, $value_definitions, ) = @_;
1535             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1536             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements::_pad_exponent', );
1537             ###LogSD $phone->talk( level => 'debug', message => [
1538             ###LogSD 'Reached _pad_exponent with:', $value_definitions, ] );
1539 48 100       91 if( $value_definitions->{exponent}->{leading_zeros} ){
1540 16         30 my $pad_string = '%0' . $value_definitions->{exponent}->{leading_zeros} . 's';
1541             $value_definitions->{exponent}->{value} =
1542 16         49 sprintf( $pad_string, sprintf( '%.0f', $value_definitions->{exponent}->{value} ) );
1543             }
1544             ###LogSD $phone->talk( level => 'debug', message => [
1545             ###LogSD 'Updated ref:', $value_definitions ] );
1546 48         67 return $value_definitions;
1547             }
1548              
1549             sub _build_fraction{
1550 460     460   390 my( $self, $value_definitions, ) = @_;
1551             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1552             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements::_build_fraction', );
1553             ###LogSD $phone->talk( level => 'debug', message => [
1554             ###LogSD 'Reached _build_fraction with:', $value_definitions, ] );
1555 460 50       828 if( $value_definitions->{decimal}->{value} ){
1556             $value_definitions->{fraction}->{value} =
1557             ( $value_definitions->{fraction}->{divisor} ) ?
1558             $self->_build_divisor_fraction(
1559             $value_definitions->{fraction}->{divisor}, $value_definitions->{decimal}->{value}
1560             ) :
1561             $self->_continued_fraction(
1562             $value_definitions->{decimal}->{value}, 20, $value_definitions->{fraction}->{target_length},
1563 460 100       1154 );
1564             }
1565 460         771 delete $value_definitions->{decimal};
1566 460   100     752 $value_definitions->{fraction}->{value} //= 0;
1567 460 100       908 if( $value_definitions->{fraction}->{value} eq '1' ){
1568 46         63 $value_definitions->{integer}->{value}++;
1569 46         62 $value_definitions->{fraction}->{value} = 0;
1570             }
1571             ###LogSD $phone->talk( level => 'debug', message => [
1572             ###LogSD 'Updated ref:', $value_definitions ] );
1573 460         553 return $value_definitions;
1574             }
1575              
1576             sub _build_divisor_fraction{
1577 276     276   339 my( $self, $divisor, $decimal ) = @_;
1578             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1579             ###LogSD $self->get_all_space . '::hidden::_build_number::_build_elements::_build_divisor_fraction', );
1580             ###LogSD $phone->talk( level => 'debug', message => [
1581             ###LogSD 'Reached _build_divisor_fraction with:', $divisor, $decimal ] );
1582 276         494 my $low_numerator = int( $divisor * $decimal );
1583 276         242 my $high_numerator = $low_numerator + 1;
1584 276         282 my $low_delta = $decimal - ($low_numerator / $divisor);
1585 276         245 my $high_delta = ($high_numerator / $divisor) - $decimal;
1586 276         174 my $return;
1587 276         213 my $add_denominator = 0;
1588 276 100       396 if( $low_delta < $high_delta ){
1589 154         128 $return = $low_numerator;
1590 154 100       245 $add_denominator = 1 if $return;
1591             }else{
1592 122         95 $return = $high_numerator;
1593 122 100       142 if( $high_numerator == $divisor ){
1594 34         43 $return = 1;
1595             }else{
1596 88         83 $add_denominator = 1;
1597             }
1598             }
1599 276 100       502 $return .= "/$divisor" if $add_denominator;
1600             ###LogSD $phone->talk( level => 'debug', message => [
1601             ###LogSD "Final fraction: $return" ] );
1602 276         472 return $return;
1603             }
1604              
1605             sub _add_integer_separator{
1606 214     214   271 my ( $self, $int, $comma, $frequency ) = @_;
1607             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1608             ###LogSD $self->get_all_space . '::hidden::_util_function::_add_integer_separator', );
1609             ###LogSD $phone->talk( level => 'info', message => [
1610             ###LogSD "Attempting to add the separator -$comma- to " .
1611             ###LogSD "the integer portion of: $int" ] );
1612 214   50     319 $comma //= ',';
1613 214         196 my @number_segments;
1614 214 50       464 if( is_Int( $int ) ){
1615 214         2276 while( $int =~ /(-?\d+)(\d{$frequency})$/ ){
1616 146         209 $int= $1;
1617 146         588 unshift @number_segments, $2;
1618             }
1619 214         308 unshift @number_segments, $int;
1620             ###LogSD $phone->talk( level => 'info', message => [
1621             ###LogSD 'Final parsed list:', @number_segments ] );
1622 214         606 return join( $comma, @number_segments );
1623             }else{
1624             ###LogSD $phone->talk( level => 'warn', message => [
1625             ###LogSD "-$int- is not an integer!" ] );
1626 0         0 return undef;
1627             }
1628             }
1629              
1630             sub _continued_fraction{# http://www.perlmonks.org/?node_id=41961
1631 184     184   192 my ( $self, $decimal, $max_iterations, $max_digits ) = @_;
1632             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1633             ###LogSD $self->get_all_space . '::hidden::_util_function::_continued_fraction', );
1634             ###LogSD $phone->talk( level => 'info', message => [
1635             ###LogSD "Attempting to build an integer fraction with decimal: $decimal",
1636             ###LogSD "Using max iterations: $max_iterations",
1637             ###LogSD "..and max digits: $max_digits", ] );
1638 184         162 my @continuous_integer_list;
1639 184         138 my $start_decimal = $decimal;
1640 184 50       398 confess "Passed bad decimal: $decimal" if !is_Num( $decimal );
1641 184   66     1455 while( $max_iterations > 0 and ($decimal >= 0.00001) ){
1642 424         336 $decimal = 1/$decimal;
1643 424         505 ( my $integer, $decimal ) = $self->_integer_and_decimal( $decimal );
1644             ###LogSD $phone->talk( level => 'info', message => [
1645             ###LogSD "The integer of the inverse decimal is: $integer",
1646             ###LogSD "The remaining decimal is: $decimal" ] );
1647 424 100 100     1330 if($integer > 999 or ($decimal < 0.00001 and $decimal > 1e-10) ){
      66        
1648             ###LogSD $phone->talk( level => 'info', message => [
1649             ###LogSD "Either I found a large integer: $integer",
1650             ###LogSD "...or the decimal is small: $decimal" ] );
1651 136 100       195 if( $integer <= 999 ){
1652 120         119 push @continuous_integer_list, $integer;
1653             }
1654 136         137 last;
1655             }
1656 288         251 push @continuous_integer_list, $integer;
1657 288         820 $max_iterations--;
1658             ###LogSD $phone->talk( level => 'info', message => [
1659             ###LogSD "Remaining iterations: $max_iterations" ] );
1660             }
1661             ###LogSD $phone->talk( level => 'info', message => [
1662             ###LogSD "The current continuous fraction integer list is:", @continuous_integer_list ] );
1663 184         290 my ( $numerator, $denominator ) = $self->_integers_to_fraction( @continuous_integer_list );
1664 184 100 100     815 if( !$numerator or ( $denominator and length( $denominator ) > $max_digits ) ){
      66        
1665 40         69 my $denom = 9 x $max_digits;
1666 40         90 my ( $int, $dec ) = $self->_integer_and_decimal( $start_decimal * $denom );
1667 40         48 $int++;
1668             ###LogSD $phone->talk( level => 'debug', message => [
1669             ###LogSD "Passing through the possibilities with start numerator: $int",
1670             ###LogSD "..and start denominator: $denom", "Against start decimal: $decimal"] );
1671 40 100       141 my $lowest = ( $start_decimal >= 0.5 ) ?
1672             { delta => (1-$start_decimal), numerator => 1, denominator => 1 } :
1673             { delta => ($start_decimal-0), numerator => 0, denominator => 1 } ;
1674 40         85 while( $int ){
1675 2322         1456 my @check_list;
1676 2322         1452 my $low_int = $int - 1;
1677 2322         1781 my $low_denom = int( $low_int/$start_decimal ) + 1;
1678 2322         7721 push @check_list,
1679             { delta => abs( $int/$denom - $start_decimal ), numerator => $int, denominator => $denom },
1680             { delta => abs( $low_int/$denom - $start_decimal ), numerator => $low_int, denominator => $denom },
1681             { delta => abs( $low_int/$low_denom - $start_decimal ), numerator => $low_int, denominator => $low_denom },
1682             { delta => abs( $int/$low_denom - $start_decimal ), numerator => $int, denominator => $low_denom };
1683 2322         2638 my @fixed_list = sort { $a->{delta} <=> $b->{delta} } @check_list;
  11590         10111  
1684             ###LogSD $phone->talk( level => 'trace', message => [
1685             ###LogSD 'Built possible list of lower fractions:', @fixed_list ] );
1686 2322 100       2982 if( $fixed_list[0]->{delta} < $lowest->{delta} ){
1687 30         32 $lowest = $fixed_list[0];
1688             ###LogSD $phone->talk( level => 'debug', message => [
1689             ###LogSD 'Updated lowest with:', $lowest ] );
1690             }
1691 2322         1473 $int = $low_int;
1692 2322         5015 $denom = $low_denom - 1;
1693             ###LogSD $phone->talk( level => 'debug', message => [
1694             ###LogSD "Attempting new possibilities with start numerator: $int",
1695             ###LogSD "..and start denominator: $denom", "Against start decimal: $decimal"] );
1696             }
1697 40         88 ($numerator, $denominator) = $self->_best_fraction( @$lowest{qw( numerator denominator )} );
1698             }
1699             ###LogSD $phone->talk( level => 'info', message => [
1700             ###LogSD (($numerator) ? "Final numerator: $numerator" : undef),
1701             ###LogSD (($denominator) ? "Final denominator: $denominator" : undef), ] );
1702 184 100 66     601 if( !$numerator ){
    100          
1703             ###LogSD $phone->talk( level => 'info', message => [
1704             ###LogSD "Fraction is below the finite value - returning undef" ] );
1705 16         30 return undef;
1706             }elsif( !$denominator or $denominator == 1 ){
1707             ###LogSD $phone->talk( level => 'info', message => [
1708             ###LogSD "Rounding up to: $numerator" ] );
1709 12         30 return( $numerator );
1710             }else{
1711             ###LogSD $phone->talk( level => 'info', message => [
1712             ###LogSD "The final fraction is: $numerator/$denominator" ] );
1713 156         1199 return $numerator . '/' . $denominator;
1714             }
1715             }
1716              
1717             # Takes a list of terms in a continued fraction, and converts them
1718             # into a fraction.
1719             sub _integers_to_fraction {# ints_to_frac
1720 184     184   216 my ( $self, $numerator, $denominator) = (shift, 0, 1); # Seed with 0 (not all elements read here!)
1721             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1722             ###LogSD $self->get_all_space . '::hidden::_util_function::_integers_to_fraction', );
1723             ###LogSD $phone->talk( level => 'info', message => [
1724             ###LogSD "Attempting to build an integer fraction with the continuous fraction list: " .
1725             ###LogSD join( ' - ', @_ ), "With a seed numerator of -0- and seed denominator of -1-" ] );
1726 184         229 for my $integer( reverse @_ ){# Get remaining elements
1727             ###LogSD $phone->talk( level => 'info', message => [ "Now processing: $integer" ] );
1728 408         437 ($numerator, $denominator) =
1729             ($denominator, $integer * $denominator + $numerator);
1730             ###LogSD $phone->talk( level => 'info', message => [
1731             ###LogSD "New numerator: $numerator", "New denominator: $denominator", ] );
1732             }
1733 184         259 ($numerator, $denominator) = $self->_best_fraction($numerator, $denominator);
1734             ###LogSD $phone->talk( level => 'info', message => [
1735             ###LogSD "Updated numerator: $numerator",
1736             ###LogSD (($denominator) ? "..and denominator: $denominator" : undef) ] );
1737 184         245 return ( $numerator, $denominator );
1738             }
1739              
1740              
1741             # Takes a numerator and denominator, in scalar context returns
1742             # the best fraction describing them, in list the numerator and
1743             # denominator
1744             sub _best_fraction{#frac_standard
1745 224     224   196 my ($self, $n, $m) = @_;
1746             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1747             ###LogSD $self->get_all_space . '::hidden::_util_function::_best_fraction', );
1748             ###LogSD $phone->talk( level => 'info', message => [
1749             ###LogSD "Finding the best fraction", "Start numerator: $n", "Start denominator: $m" ] );
1750 224         242 $n = $self->_integer_and_decimal($n);
1751 224         271 $m = $self->_integer_and_decimal($m);
1752             ###LogSD $phone->talk( level => 'info', message => [
1753             ###LogSD "Updated numerator and denominator ( $n / $m )" ] );
1754 224         314 my $k = $self->_gcd($n, $m);
1755             ###LogSD $phone->talk( level => 'info', message => [ "Greatest common divisor: $k" ] );
1756 224         211 $n = $n/$k;
1757 224         161 $m = $m/$k;
1758             ###LogSD $phone->talk( level => 'info', message => [
1759             ###LogSD "Reduced numerator and denominator ( $n / $m )" ] );
1760 224 50       328 if ($m < 0) {
1761             ###LogSD $phone->talk( level => 'info', message => [ "the divisor is less than zero" ] );
1762 0         0 $n *= -1;
1763 0         0 $m *= -1;
1764             }
1765 224 100       312 $m = undef if $m == 1;
1766             ###LogSD no warnings 'uninitialized';
1767             ###LogSD $phone->talk( level => 'info', message => [
1768             ###LogSD "Final numerator and denominator ( $n / $m )" ] );
1769             ###LogSD use warnings 'uninitialized';
1770 224 50       262 if (wantarray) {
1771 224         359 return ($n, $m);
1772             }else {
1773 0 0       0 return ( $m ) ? "$n/$m" : $n;
1774             }
1775             }
1776              
1777             # Takes a number, returns the best integer approximation and
1778             # (in list context) the error.
1779             sub _integer_and_decimal {# In the future see if this will merge with _split_decimal_integer
1780 912     912   713 my ( $self, $decimal ) = @_;
1781             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1782             ###LogSD $self->get_all_space . '::hidden::_util_function::_integer_and_decimal', );
1783             ###LogSD $phone->talk( level => 'info', message => [
1784             ###LogSD "Splitting integer from decimal for: $decimal" ] );
1785 912         701 my $integer = int( $decimal );
1786             ###LogSD $phone->talk( level => 'info', message => [ "Integer: $integer" ] );
1787 912 100       926 if(wantarray){
1788 464         776 return($integer, $decimal - $integer);
1789             }else{
1790 448         476 return $integer;
1791             }
1792             }
1793              
1794             # Euclidean algorithm for calculating a GCD.
1795             # Takes two integers, returns the greatest common divisor.
1796             sub _gcd {
1797 224     224   215 my ($self, $n, $m) = @_;
1798             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1799             ###LogSD $self->get_all_space . '::hidden::_util_function::_gcd', );
1800             ###LogSD $phone->talk( level => 'info', message => [
1801             ###LogSD "Finding the greatest common divisor for ( $n and $m )" ] );
1802 224         314 while ($m) {
1803 576         428 my $k = $n % $m;
1804             ###LogSD $phone->talk( level => 'info', message => [
1805             ###LogSD "Remainder after division: $k" ] );
1806 576         832 ($n, $m) = ($m, $k);
1807             ###LogSD $phone->talk( level => 'info', message => [
1808             ###LogSD "Updated factors ( $n and $m )" ] );
1809             }
1810 224         241 return $n;
1811             }
1812              
1813             around set_error => sub{
1814             my ($orig, $self, $error) = @_;
1815              
1816             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1817             ###LogSD $self->get_all_space . '::set_error', );
1818             ###LogSD $phone->talk( level => 'info', message => [
1819             ###LogSD "Recording the error:", $error ] );
1820             if( $self->_has_workbook_inst ){
1821             $self->$orig( $error );
1822             }else{
1823             print longmess( $error );
1824             }
1825             };
1826              
1827             around get_epoch_year => sub{
1828             my ($orig, $self, ) = @_;
1829             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
1830             ###LogSD $self->get_all_space . '::get_epoch_year', );
1831             ###LogSD $phone->talk( level => 'info', message => [
1832             ###LogSD "Looking for the epoch year" ] );
1833             if( $self->_has_workbook_inst ){
1834             return $self->$orig;
1835             }
1836             return $self->_get_alt_epoch_year;
1837             };
1838              
1839             #########1 Phinish 3#########4#########5#########6#########7#########8#########9
1840              
1841 2     2   14229 no Moose::Role;
  2         3  
  2         27  
1842              
1843             1;
1844              
1845             #########1 Documentation 3#########4#########5#########6#########7#########8#########9
1846             __END__
1847              
1848             =head1 NAME
1849              
1850             Spreadsheet::Reader::Format::ParseExcelFormatStrings - Convert Excel format strings to code
1851              
1852             =head1 SYNOPSYS
1853              
1854             #!/usr/bin/env perl
1855             package MyPackage;
1856             use Moose;
1857              
1858             use lib '../../../../lib';
1859             extends 'Spreadsheet::Reader::Format::FmtDefault';
1860             with 'Spreadsheet::Reader::Format::ParseExcelFormatStrings';
1861              
1862             package main;
1863              
1864             my $parser = MyPackage->new( epoch_year => 1904 );
1865             my $conversion = $parser->parse_excel_format_string( '[$-409]dddd, mmmm dd, yyyy;@' );
1866             print 'For conversion named: ' . $conversion->name . "\n";
1867             for my $unformatted_value ( '7/4/1776 11:00.234 AM', 0.112311 ){
1868             print "Unformatted value: $unformatted_value\n";
1869             print "..coerces to: " . $conversion->assert_coerce( $unformatted_value ) . "\n";
1870             }
1871              
1872             ###########################
1873             # SYNOPSIS Screen Output
1874             # 01: For conversion named: DATESTRING
1875             # 02: Unformatted value: 7/4/1776 11:00.234 AM
1876             # 03: ..coerces to: Thursday, July 04, 1776
1877             # 04: Unformatted value: 0.112311
1878             # 05: ..coerces to: Monday, January 01, 1900
1879             ###########################
1880              
1881             =head1 DESCRIPTION
1882              
1883             This is the parser that converts Excel custom format strings into code that can be used
1884             to transform values into output matching the form defined by the format string. The goal
1885             of this code is to support as much as possible the definition of L<excel custom format strings
1886             |https://support.office.com/en-us/article/Create-or-delete-a-custom-number-format-78f2a361-936b-4c03-8772-09fab54be7f4>.
1887             If you find cases where this parser and the Excel definition or excecution differ please
1888             log a case in L<github|https://github.com/jandrew/Spreadsheet-XLSX-Reader-LibXML/issues>.
1889              
1890             This parser converts the format strings to L<Type::Tiny> objects that have the appropriate
1891             built in coercions. Any replacement of this engine for use with
1892             L<Spreadsheet::Reader::Format> I<and L<Spreadsheet::Reader::ExcelXML>> must output objects
1893             that have the methods 'display_name' and 'assert_coerce'. 'display_name' is used by the
1894             overall package to determine the cell type and should return a unique name containing an
1895             indication of the output data type with either 'DATE' or 'NUMBER' in the name. Otherwise
1896             the cell type is assumed to be text. L<Spreadsheet::Reader::ExcelXML> uses 'assert_coerce'
1897             as the method to transform the raw value to the formatted value.
1898              
1899             Excel format strings can have up to four parts separated by semi-colons. The four parts
1900             are positive, zero, negative, and text. In the Excel application the text section is just
1901             a pass through. I<This is how excel handles L<dates earlier than 1900sh
1902             |DateTimeX::Format::Excel/DESCRIPTION>>. This parser deviates from that for dates. Since
1903             this parser provides code that parses Excel date numbers into a L<DateTime> object (and
1904             then L<potentially back|/datetime_dates> to a differently formatted string) it also
1905             attempts to parse strings to DateTime objects if the cell has a date format applied. All
1906             other types of Excel number conversions still treat strings as a pass through.
1907              
1908             To replace this module just build a L<Moose::Role|Moose::Manual::Roles> that delivers
1909             the method L<parse_excel_format_string|/parse_excel_format_string( $string, $name )> and
1910             L<get_defined_conversion|/get_defined_conversion( $position )>. See the L<documentation
1911             |Spreadsheet::Reader::Format::FormatInterface> for the format interface to integrate into
1912             the package.
1913              
1914             =head2 Caveat Utilitor
1915              
1916             The decimal (real number) to fraction conversion implementation here is processing
1917             intensive. I erred on the side of accuracy over speed. While I tried my best to
1918             provide equivalent accuracy to the Excel output I was unable to duplicate the results
1919             in all cases. In those cases this package provides a more precise result than Excel.
1920             If you are experiencing delays when reading fraction formatted values then this package
1921             is a place to investigate. In order to get the most accurate answer this parser
1922             initially uses the L<continued fraction|http://en.wikipedia.org/wiki/Continued_fraction>
1923             algorythm to calculate a possible fraction for the pased $decimal value with the
1924             setting of 20 max iterations and a maximum denominator width defined by the format
1925             string. If that does not resolve satisfactorily it then calculates -all- over/under
1926             numerators with decreasing denominators from the maximum denominator (based on the
1927             format string) all the way to the denominator of 2 and takes the most accurate result.
1928             There is no early-out available in this computation so if you reach this point for multi
1929             digit denominators things slow down. (Not that continued fractions are
1930             computationally so cheap.) However, dual staging the calculation this way yields either
1931             the same result as Excel or a more accurate result while providing a possible early out
1932             in the continued fraction portion. I was unable to even come close to Excel output
1933             otherwise. If you have a faster conversion or just want to opt out for specific cells
1934             without replacing this whole parser then use the worksheet method
1935             L<Spreadsheet::Reader::ExcelXML::Worksheet/set_custom_formats( $key =E<gt> $format_object_or_string )>.
1936             I<hint: $format_object_or_string = '@' will set a pass through.>
1937              
1938             =head2 requires
1939              
1940             These are method(s) used by this role but not provided by the role. Any class consuming this
1941             role will not build without first providing this(ese) methods prior to loading this role.
1942              
1943             =head3 get_defined_excel_format
1944              
1945             =over
1946              
1947             B<Definition:> Used to return the standard error string for a defined format position.
1948              
1949             See L<Spreadsheet::Reader::Format::FmtDefault/defined_excel_translations>
1950              
1951             =back
1952              
1953             =head2 Methods
1954              
1955             These are the methods provided by this role to whatever class or instance inherits this
1956             role. For additional ParseExcelFormatStrings options see the L<Attributes|/Attributes>
1957             section.
1958              
1959             =head3 parse_excel_format_string( $string, $name )
1960              
1961             =over
1962              
1963             B<Definition:> This is the method to convert Excel L<format strings
1964             |https://support.office.com/en-us/article/Create-or-delete-a-custom-number-format-83657ca7-9dbe-4ee5-9c89-d8bf836e028e?ui=en-US&rs=en-US&ad=US>
1965             into L<Type::Tiny> objects with built in coercions. The type coercion objects are then used to
1966             convert L<unformatted|Spreadsheet::Reader::Format::Cell/unformatted> values into formatted
1967             values using the L<assert_coerce|Type::Coercion/Coercion> method. Coercions built by this module
1968             allow for the format string to have up to four parts separated by semi-colons. These four parts
1969             correlate to four different data input ranges. The four parts are positive, zero, negative, and
1970             text. If three substrings are sent then the data input is split to (positive and zero), negative,
1971             and text. If two input types are sent the data input is split between numbers and text. One input
1972             type is a take all comers type with the exception of dates. When dates are built by this module it
1973             always adds a possible from-text conversion to process Excel pre-1900ish dates. This is because
1974             Excel does not record dates prior to 1900ish as numbers. All date unformatted values are then
1975             processed into and then L<potentially|/datetime_dates> back out of L<DateTime> objects. This
1976             requires L<Type::Tiny::Manual::Coercions/Chained Coercions>. The two packages used for conversion
1977             to DateTime objects are L<DateTime::Format::Flexible> and L<DateTimeX::Format::Excel>.
1978              
1979             B<Accepts:> an Excel number L<format string
1980             |https://support.office.com/en-us/article/Create-or-delete-a-custom-number-format-83657ca7-9dbe-4ee5-9c89-d8bf836e028e?ui=en-US&rs=en-US&ad=US>
1981             and a conversion name stored in the Type::Tiny object. This package will auto-generate a name if
1982             none is given
1983              
1984             B<Returns:> a L<Type::Tiny> object with type coercions and pre-filters set for each input type
1985             from the formatting string
1986              
1987             =back
1988              
1989             =head3 get_defined_conversion( $position )
1990              
1991             =over
1992              
1993             B<Definition:> This is a helper method that combines the call to
1994             L<Spreadsheet::Reader::Format::FmtDefault/get_defined_excel_format( $position )> and
1995             parse_excel_format_string above in order to get the final result with one call.
1996              
1997             B<Accepts:> an Excel default format position
1998              
1999             B<Returns:> a L<Type::Tiny> object with type coercions and pre-filters set for each input type
2000             from the formatting string
2001              
2002             =back
2003              
2004             =head2 Attributes
2005              
2006             Data passed to new when creating a class or instance containing this role. For
2007             modification of these attributes see the listed 'attribute methods'. For more
2008             information on attributes see L<Moose::Manual::Attributes>.
2009              
2010             =head3 workbook_inst
2011              
2012             =over
2013              
2014             B<Definition:> This role works better if it has access to two workbook methods
2015             there are defaults built in if the workbook is not connected but the package no
2016             longer responds dynamically when that connection is broken. This instance is a
2017             way for this role to see those settings.
2018              
2019             B<Required:> No but it's really nice
2020              
2021             B<Range:> an instance of the L<Spreadsheet::Reader::ExcelXML> class
2022              
2023             B<attribute methods> Methods provided to adjust this attribute
2024              
2025             =over
2026              
2027             B<set_workbook_inst( $instance )>
2028              
2029             =over
2030              
2031             B<Definition:> sets the workbook instance
2032              
2033             =back
2034              
2035             =back
2036              
2037             B<delegated methods> Methods provided from the object stored in the attribute
2038              
2039             method_name => method_delegated_from_link
2040              
2041             =over
2042              
2043             B<set_error( $error_string )> => L<Spreadsheet::Reader::ExcelXML/set_error>
2044              
2045             B<get_epoch_year> => L<Spreadsheet::Reader::ExcelXML/get_epoch_year>
2046              
2047             =back
2048              
2049             =back
2050              
2051             =head3 cache_formats
2052              
2053             =over
2054              
2055             B<Definition:> In order to save re-building the coercion each time they are
2056             requested, the built coercions can be cached with the format string as the key.
2057             This attribute sets whether caching is turned on or not. In rare cases with
2058             lots of unique formats this would allow a reduction in RAM consumtion at the
2059             price of speed.
2060              
2061             B<Range:> Boolean
2062              
2063             B<Default:> 1 = caching is on
2064              
2065             B<attribute methods> Methods provided to adjust this attribute
2066              
2067             =over
2068              
2069             B<get_cache_behavior>
2070              
2071             =over
2072              
2073             B<Definition:> returns the state of the attribute
2074              
2075             =back
2076              
2077             B<set_cache_behavior( $bool )>
2078              
2079             =over
2080              
2081             B<Definition:> sets the value of the attribute to $Bool
2082              
2083             B<Range:> Boolean 1 = cache formats, 0 = Don't cache formats
2084              
2085             =back
2086              
2087             =back
2088              
2089             =back
2090              
2091             =head3 datetime_dates
2092              
2093             =over
2094              
2095             B<Definition:> It may be that you desire the full L<DateTime> object as output
2096             rather than the finalized datestring when converting unformatted date data to
2097             formatted date data. This attribute sets whether data coersions are built to do
2098             the full conversion or just to a DateTime object in return.
2099              
2100             B<Default:> 0 = unformatted values are coerced completely to date strings (1 =
2101             stop at DateTime objects)
2102              
2103             B<attribute methods> Methods provided to adjust this attribute.
2104              
2105             =over
2106              
2107             B<get_date_behavior>
2108              
2109             =over
2110              
2111             B<Definition:> returns the value of the attribute
2112              
2113             =back
2114              
2115             =back
2116              
2117             =over
2118              
2119             B<set_date_behavior( $bool )>
2120              
2121             =over
2122              
2123             B<Definition:> sets the attribute value (only L<new|/cache_formats> coercions
2124             are affected)
2125              
2126             B<Accepts:> Boolean values
2127              
2128             B<Delegated to the workbook class:> yes
2129              
2130             =back
2131              
2132             =back
2133              
2134             =back
2135              
2136             =head3 european_first
2137              
2138             =over
2139              
2140             B<Definition:> This is a way to check for DD-MM-YY formatting of
2141             inbound (read from the file) date stringsprior to checking for MM-DD-YY.
2142             Since the package always checks both ways when the number is ambiguous
2143             the goal is to catch data where the substring for DD < 13 and assign it
2144             correctly.
2145              
2146             B<Default:> 0 = MM-DD-YY[YY] is tested first
2147              
2148             B<attribute methods> Methods provided to adjust this attribute
2149              
2150             =over
2151              
2152             B<get_european_first>
2153              
2154             =over
2155              
2156             B<Definition:> returns the value of the attribute
2157              
2158             =back
2159              
2160             B<set_european_first( $bool )>
2161              
2162             =over
2163              
2164             B<Definition:> sets the value of the attribute
2165              
2166             B<Range:> Boolean 0 = MM-DD-YY is tested first, 1 = DD-MM-YY is tested first
2167              
2168             =back
2169              
2170             =back
2171              
2172             =back
2173              
2174             =head1 SUPPORT
2175              
2176             =over
2177              
2178             L<github Spreadsheet::Reader::Format/issues
2179             |https://github.com/jandrew/p5-spreadsheet-reader-format/issues>
2180              
2181             =back
2182              
2183             =head1 TODO
2184              
2185             =over
2186              
2187             B<1.> Attempt to merge _split_decimal_integer and _integer_and_decimal
2188              
2189             =back
2190              
2191             =head1 AUTHOR
2192              
2193             =over
2194              
2195             =item Jed Lund
2196              
2197             =item jandrew@cpan.org
2198              
2199             =back
2200              
2201             =head1 COPYRIGHT
2202              
2203             This program is free software; you can redistribute
2204             it and/or modify it under the same terms as Perl itself.
2205              
2206             The full text of the license can be found in the
2207             LICENSE file included with this module.
2208              
2209             This software is copyrighted (c) 2016 by Jed Lund
2210              
2211             =head1 DEPENDENCIES
2212              
2213             =over
2214              
2215             L<Spreadsheet::Reader::Format>
2216              
2217             =back
2218              
2219             =head1 SEE ALSO
2220              
2221             =over
2222              
2223             L<Spreadsheet::ParseExcel> - Excel 2003 and earlier
2224              
2225             L<Spreadsheet::XLSX> - 2007+
2226              
2227             L<Spreadsheet::ParseXLSX> - 2007+
2228              
2229             L<Log::Shiras|https://github.com/jandrew/Log-Shiras>
2230              
2231             =over
2232              
2233             All lines in this package that use Log::Shiras are commented out
2234              
2235             =back
2236              
2237             =back
2238              
2239             =cut
2240              
2241             #########1#########2 main pod documentation end 5#########6#########7#########8#########9