File Coverage

blib/lib/String/PictureFormat.pm
Criterion Covered Total %
statement 317 742 42.7
branch 140 682 20.5
condition 51 296 17.2
subroutine 8 10 80.0
pod 5 5 100.0
total 521 1735 30.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             String::PictureFormat - Functions to format and unformat strings based on a "Picture" format string
4              
5             =head1 AUTHOR
6              
7             Jim Turner
8              
9             (c) 2015, Jim Turner under the same license that Perl 5 itself is. All rights reserved.
10              
11             =head1 SYNOPSIS
12              
13             use String::PictureFormat;
14              
15             $_ = fmt('@"...-..-...."', 123456789);
16             print "-formatted=$_=\n"; #RETURNS "123-45-6789".
17              
18             $_ = unfmt('@"...-..-...."', '123-45-6789');
19             print "-unformatted=$_=\n"; #RETURNS "123456789".
20              
21             $_ = fmt('@$,12.2>', 123456789);
22             print "-formatted=$_= \n"; #RETURNS " $123,456,789.00".
23              
24             $_ = fmtsiz('@$,12.2>');
25             print "-format size=$_= \n"; #RETURNS 18.
26              
27             $_ = fmt('@$,12.2> CR', -123456789);
28             print "-formatted=$_=\n"; #RETURNS " $123,456,789.00 CR".
29              
30             $_ = fmt('@$,12.2> CR', 123456789);
31             print "-formatted=$_=\n"; #RETURNS " $123,456,789.00 ".
32              
33             $_ = fmt('@$,12.2>', -123456789);
34             print "-formatted=$_=\n"; #RETURNS " $-123,456,789.00".
35              
36             $_ = fmt('@-$,12.2>', -123456789);
37             print "-formatted=$_=\n"; #RETURNS " -$123,456,789.00".
38              
39             $_ = fmt('@$(,12.2>)', -123456789);
40             print "-formatted=$_=\n"; #RETURNS " $(123,456,789.00)".
41              
42             $_ = fmt('=16<', 'Now is the time for all good men to come to the aid of their country');
43             print "-s=".join('|',@{$s})."=\n"; #RETURNS "Now is the time |for all good men |to come to the aid|of their country =".
44              
45             sub foo {
46             (my $data = shift) =~ tr/a-z/A-Z/;
47             return $data;
48             }
49             ...
50             $_ = fmt('@foo()', 'Now is the time for all');
51             print "-formatted=$_=\n"; #RETURNS "NOW IS THE TIME FOR ALL"
52              
53             $_ = fmt('@tr/aeiou/AEIOU/', 'Now is the time for all');
54             print "-formatted=$_=\n"; #RETURNS "NOw Is thE tImE fOr All"
55              
56             =head1 DESCRIPTION
57              
58             String::PictureFormat provides functions to format and unformat character strings according to separate
59             format strings made up of special characters. Typical usage includes left and right justification,
60             centering, floating dollar signs, adding commas to numbers, formatting phone numbers, Social-Security
61             numbers, converting negative numbers to accounting notations, creating text files containing tables
62             of data in fixed-column format, etc.
63              
64             =head1 EXAMPLES
65              
66             See B
67              
68             =head1 FORMAT STRINGS
69              
70             Format strings consist of special characters, explained in detail below. Each format string begins
71             with one of the following characters: "@", "=", or "%". "@" indicates a standard format string that
72             can be any one of several different formats as described in detail below. "=" indicates a format
73             that will "wrap" the text to be formatted into multiple rows. "%" indicates a standard C-language
74             "printf" format string.
75              
76             =over 4
77              
78             =item "@"-format strings:
79              
80             The standard format strings that begin with an "@" sign can be in one of the following formats:
81              
82             1) @"literal-picture-string" or @'literal-picture-string' or @/literal-picture-string/ or @`literal-picture-string`
83              
84             =over 4
85              
86             This format does a character-by-character converstion of the data. They can be escaped with "\"
87             to include as literals, if needed. The special characters are:
88              
89             "." - return the next character in the data.
90             "^" - skip the next character in the data.
91             "+" - return all remaining characters in the string.
92              
93             For example, to convert an integer number to a phone number with area code, one could do:
94              
95             my $ph = fmt('@"(...) ...-.+"', '1234567890 x101');
96             print "-phone# $ph\n"; #-phone# (123) 456-7890 x101
97              
98             Or, to format a social security number and return a string of asterisks if it is too long:
99              
100             my $ss = fmt('@"...-..-...."', '123456789', {-truncate => 'error'});
101             print "-ssn: $ss\n" #-ssn: 123-45-6789
102              
103             Now suppose you had part numbers where the 3rd character was a letter and the rest were digits
104             and you want only the digits, you could do:
105              
106             my $partseq = fmt('@"..^.+"', '12N345');
107             print "-part# $partseq\n" #-part# 12345
108              
109             =back
110              
111             2) @justification-string
112              
113             =over 4
114              
115             This consists of the special characters "<", "|", and ">", with optional numbers preceeding them
116             to indicate repetition, an optional decimal point, an optional prefix of "floating" characters,
117             and / or an optional suffix of literal characters. Each of the first three characters shown above
118             represent a single character of data to be returned and correspond to "left-justify", "center", or
119             "right-justify" the data returned. For example, the most basic format is:
120              
121             my $str = fmt('@>>>>>>>>>', 'Howdy');
122             print "-formatted=$str=\n"; #-formatted= Howdy=
123              
124             This returns a 10-character string right-justified (note that the "@" sign counts as one of the
125             characters representing the size of the field). This could've also been abbreviated as:
126              
127             my $str = fmt('@9>', 'Howdy');
128              
129             You can mix and match the three special characters, but the first one determines justification.
130             The only exception to this is if a decimal point is provided and the data is numeric. In that
131             case, if ">" is used after the decimal point, trailing decimal places will be rounded and removed
132             if necessary to get the string to fit, otherwise, either asterisks are returned if it won't fit
133             and the "-truncate => 'error'" option is specified. The decimal point is explicit, not implied.
134             This means that a number will be returned as that value with any excess decimal places removed or
135             zeros added to format it to the given format. For example:
136              
137             fmt('@6.2>', 123.456) will return " 123.46" (ten characters wide, right justified with two
138             decimal places). The total width is ten, due to the fact that there are 6 digits left of the
139             decimal + 2 decimal places + the decimal point + the "@" sign = 10. The full format could've
140             been given as "@>>>>>>.>>".
141              
142             Characters between the "@" sign and the first justification character are considered "floating"
143             characters and anything after the last one is a literal suffix. The main uses for the suffix
144             is to specify negative numbers in accounting format. Here's some examples:
145              
146             fmt('@$6.2>', 123.456) will return " $123.45" (eleven characters wide with a floating "$"-
147             sign. The field width is eleven instead of ten due to a space being provided for the floating
148             character.
149              
150             Commas are a special floating character, as they will be added to large numbers automatically
151             as needed, if specified. Consider:
152              
153             fmt('@$,8.2>', 1234567) will return " $1,234,567.00". Fifteen characters are returned:
154             9 for the whole number, 1 for the decimal point, 2 decimal places, the "@" sign, the "$" sign,
155             and one for each "," added.
156              
157             There are several ways to format egative numbers. For example, the default is to just leave
158             the negative number sign intact. In the case above, the result would've been:
159             " $-1,234,567.00". This could be changed to " -$1,234,567.00" by including the "-" sign as
160             a float character before the floating "$" sign, ie. fmt('@-$,8.2>', 1234567). Note that
161             the string is now sixteen characters long with the addition of another float character. Also
162             note that had the number been positive, the "-" would've been omitted automatically from the
163             returned result! You can force a sign to be displayed (either "+" or "-" depending on
164             whether the input data is a positive or negative number) by using a floating "+" instead of
165             the floating "-".
166              
167             If you are formatting numbers for accounting or tax purposes, there are special float and
168             suffix characters for that too. For examples:
169              
170             fmt('@$,8.2>CR', -123456.7) will return " $123,456.70CR". The "CR" is replaced by " " if
171             the input data is zero or positive. To get a space between the number and the "CR", simply
172             add a space to the suffix, ie. "@$,8.2> CR".
173              
174             Another common accounting format is parenthesis to indicate negative numbers. This is
175             accomplished by combining the special float character "(" with a suffix that starts with a
176             ")". For example:
177              
178             fmt('@($,8.2>)', -123456.7) will return " ($123,456.70)". The parenthesis will be replaced
179             by spaces if the number is zero or positive. However, the space in lieu of the "(" may
180             instead be replaced by an extra digit if the number is large and just barely fits. If one
181             desires to have the "$" sign before the parenthesis, simply do "fmt('@$(,8.2>)', -123456.7)"
182             instead! Note that "+" and "-" should not be floated when using parenthesis or "CR" notation.
183              
184             Since floating characters, particularly floating commas, and negative numbers can increase
185             the width of the returned value causing variations in width; if you are needing to create
186             columns of fixed width, an absolute width size can be specified (along with the
187             "{-truncate => 'error'}" option. This is given as a numeric value followed by a colon
188             immediately following the "@" sign, for example:
189              
190             fmt('@16:($,8.2>)', -123456.7, {-truncate => 'error'})
191              
192             This forces the returned value to be either 16 characters right-justified or 16 "*"'s to be
193             returned. You should be careful to anticipate the maximum size of your data including any
194             floating characters to be added.
195              
196             =back
197              
198             3) @^date/time-picture-string^ (Date / Time Conversions):
199              
200             =over 4
201              
202             This format does a character-by-character converstion of date / time data based on certain
203             substrings of special characters. The list of special character strings are described in
204             L. If this optional module is not installed, then the following are
205             available:
206              
207             B - Year in 4 digits.
208              
209             B, B - Year in last 2 digits.
210              
211             B - Number of month (2 digits, left padded with a zero if needed), ie. "01" for January.
212              
213             B
- Day of month (2 digits, left padded with a zero if needed), ie. "01".
214              
215             B, B - Hour in 24-hour format, 2 digits, left padded with a zero if needed, ie. 00-23.
216              
217             B - Minute, ie. 00-59.
218              
219             B - Seconds since start of last minute (2 digits), ie. 00-59.
220              
221             A valid date string will be formatted / unformatted based on the I. If
222             B and B are installed, the "valid date string" being
223             processed by B() can be, and the output produced by B() will be a Perl/Unix time
224             integer. Otherwise, the other valid data strings processed by B() are
225             "yyyymmdd [hhmmss]", "mm-dd-yyyy [hh:mm:ss]", etc. B() will return
226             "yyyymmdd [hhmm[ss]" unless B is installed, in which case, it returns
227             a Perl/Unix time integer. This can be changed using B<-outfmt>. NOTE: It is highly
228             recommended that both of these modules be installed if formatting or unformatting
229             date / time values, as the manual workarounds used do not always produce desired results.
230              
231             Examples:
232              
233             fmt('@^mm-dd-yy^, 20150108) will return "01-08-15".
234              
235             fmt('@^mm-dd-yy hh:mi^, '01-08-2015 10:25') will return "01-08-15 10:25".
236              
237             fmt('@^mm-dd-yy^, '2015/01/08') will return "01-08-15".
238              
239             fmt('@^mm-dd-yy^, 1420781025) will return "01-08-15", if B is installed.
240              
241             unfmt('@^mm-dd-yy^, '01-08-15') will return "20150108" unless B is
242             installed, in which case it will return 1420696800 (equivalent to "2015/01/08 00:00:00".
243              
244             unfmt('@^mm-dd-yy^, '01-08-15', {-outfmt => 'yyyymmdd'}) will always return "20150108",
245             if B is also installed.
246              
247             =back
248              
249             4) Regex substitution:
250              
251             =over 4
252              
253             This format specifies a Perl "regular expression" to perform in the input data and outputs
254             the result. For example:
255              
256             $s = fmt('@s/[aeiou]/\[VOWEL\]/ig;', 'Now is the time for all');
257             would return:
258             "N[VOWEL]w [VOWEL]s th[VOWEL] t[VOWEL]m[VOWEL] f[VOWEL]r [VOWEL]ll".
259              
260             The new string is returned as-is regardless of length. To truncate it to a maximum fixed
261             length, specify a length constraint. You can also specify the "-truncate => 'error'
262             option to return a row of "*" of that length if the resulting string is longer, ie:
263             $s = fmt('@50:s/[aeiou]/\[VOWEL\]/ig;', 'Now is the time for all', {-truncate => 'error'});
264              
265             Perl's Translate (tr) function is also supported, ie:
266              
267             $s = fmt('@tr/aeiou/AEIOU/', 'Now is the time for all');
268             would return "NOw Is thE tImE fOr All".
269              
270             =back
271              
272             5) User-supplied functions:
273              
274             =over 4
275              
276             You can write your own custum translate function for full control over the data translation.
277             You can also supply any arguments to it that you wish, however two special ones are
278             provided for your use: "*" and "#". If you do not pass any parameters to the function,
279             then it will be called with "(*,#)". "*" represents the input data string and "#"
280             represents the maximum length to be returned (if not specified, it is zero, which means
281             the returned string may be any length. For example:
282              
283             $s = fmt('@foo', 'Now is the time for all');
284             print "-s=$s=\n";
285             ...
286             sub foo {
287             my ($data, $maxlength) = @_;
288             print "-max. length=$maxlength= just=$just= data in=$data=\n";
289             $data =~ tr/a-z/A-Z/;
290             return $data;
291             }
292              
293             This would return "NOW IS THE TIME FOR ALL". This is the same as:
294             $s = fmt('@foo(*,#)', 'Now is the time for all');
295              
296             To call a function with just the $data parameter, do:
297              
298             $s = fmt('@foo(*)', 'Now is the time for all');
299              
300             To specify a maximum length, say "50" do:
301              
302             $s = fmt('@50:foo', 'Now is the time for all', {-truncate => 'error'});
303              
304             To append a suffix string ("suffix" in the example, not counted in the max. length) do:
305              
306             $s = fmt('@foo()suffix', 'Now is the time for all');
307              
308             which would return "NOW IS THE TIME FOR ALLsuffix".
309              
310             =back
311              
312             =item "="-format strings:
313              
314             These specify text "wrapping" for long strings of characters. Data can be wrapped at either
315             character or word boundaries. The default is to wrap by word. Consider:
316              
317             $s = fmt('=15<', 'Now is the time for all good men to come to the aid of their country');
318             print "-s=".join('|',@{$s})."=\n";
319              
320             This will print:
321             "-s=Now is the time |for all good men|to come to the |aid of their |country "
322             The function returned the data as a reference to an array, each element containing a "row"
323             or "line" of 16 characters of data broken on the nearest "word boundary" and left-justified.
324             Each "row" is right-padded with spaces to bring it to 16 characters (the "=" sign plus the
325             "15" represents a row width of 16 characters. I use "|" to show the boundary between each
326             row/line.
327              
328             $s = fmt('=15>', 'Now is the time for all good men to come to the aid of their country');
329             would've returned (right-justified):
330             " Now is the time|for all good men| to come to the| aid of their| country"
331              
332             $s = fmt('=15|', 'Now is the time for all good men to come to the aid of their country');
333             would've returned (centered):
334             " Now is the time|for all good men| to come to the | aid of their | country "
335              
336             To specify simple character wrapping (spaces remain intact), one can add "w" to the
337             format string like so:
338              
339             $s = fmt('=w14<', 'Now is the time for all good men to come to the aid of their country');
340             This would return:
341             "Now is the time |for all good men| to come to the |aid of their cou|ntry "
342             NOTE: The change of "15" to "14". This is due to the fact that the "w" adds one to the
343             row "size"!
344              
345             With "w" (character wrapping), justification is pretty meaningless since each row (except
346             the last) will always contain the full number of characters with spaces as-is (no
347             spaces added). However, the last row will be affected if spaces have to be added to fill
348             it out. To get the string represented "properly", it's usually best to use "<" (left-
349             justification).
350              
351             The default is "word" wrapping, so a format string of "=15<" is the same as "=W14<".
352              
353             =item "%" (C-language) format strings:
354              
355             You can specify a C/Perl language "printf" format string by preceeding it with a "%" sign.
356             For example:
357              
358             fmt('%-12.2d', -1234);
359              
360             returns "-1234 "
361              
362             There is the added capability of floating "$" sign and commas. For example:
363              
364             fmt('%$,12.2f', -1234) returns " $-1,234.00". Note the width is 14 instead of 12
365             characters, since the two floating characters add to the width of the final results.
366             The "$" sign and "," are the only floating character options.
367              
368             =back
369              
370             =head1 METHODS
371              
372             =over 4
373              
374             =item <$scalar> || <@array> = B(I, I [, I ]);
375              
376             Returns either a formatted string (scalar) or an array of values. The
377             is applied to the to convert it to a new format (see the myriad of
378             examples in this documentation). If the specified return value is in ARRAY
379             context, the elements are:
380              
381             [0] - The string or array reference returned in the scalar context ("wrap" formats
382             return an array reference, and all others return a string).
383              
384             [1] - The length (integer) of the data formatted - note that this is not always the actual
385             length of the returned data. It represents the maximum "format length", which is
386             the max. no. of characters the format can return. If the format is open-ended,
387             ie. if the last character in a fixed format is "+", or the length is indeterminate,
388             it will return zero. For "wrap" formats, it is the no. of characters in a row.
389             If a max. length specifier is given (ie. "@50:..."), then this value is returned.
390              
391             [2] - The justification (either "<", "|", ">", or "", if no justification is
392             involved).
393              
394             I is the format string (required).
395              
396             I is the data to be formatted (required).
397              
398             I is an optional hash-reference representing additional options. The
399             currently valid options are:
400              
401             =over 4
402              
403             B<-bad> => '' (default '*') - The character to fill the output string if the
404             output string exceeds the specified maximum length and <-truncate> => 'error' is
405             specified.
406              
407             B<-nonnumeric> => true | false (default false or 0) - whether or not to ignore
408             "numeric"-specific formatting, ie. adding commas, sign indicators, decimal places,
409             etc. even if the data is "numeric".
410              
411             B<-sizefixed> => true | false (default false or 0) - If true, prevents expansion of
412             certain numeric formats when the number is positive or more than one comma is added.
413             What it actually does is set the format size to be fixed to the value returned by
414             B() for the specified I. This ensures that the format
415             size will be the same reguardless of what value is passed to it.
416              
417             B<-suffix> => '[yes]' | 'no' (default yes) - If 'no', then any suffix string is
418             ignored (not appended) when formatting and not removed when unformatting. Specifying
419             anything but "no" implies the default of yes.
420              
421             B<-truncate> => '[yes]' | 'no' | 'er[ror]' - Whether or not to truncate output
422             data that exceeds the maximum width. The default is 'yes'. Specifying 'no' means
423             return the entire output string regardless of length. 'er', 'err', 'error', etc.
424             means return a row of asterisks (changable by B<-bad>). If the string does not
425             begin with "no" or "er", it is assumed to be "yes".
426              
427             =back
428              
429             =item <$scalar> || <@array> = B(I, I [, I ]);
430              
431             For the most part, this is the opposite of the B() function. It takes a
432             string and attempts to "undo" the format and return the data as close as
433             possible to what the input data string would've looked like before the
434             was applied by assuming that the input is the
435             result of having previously had that applied to it by B().
436             It is not always possible to exactly undo the format, consider:
437              
438             my $partseq = fmt('@"..^.+"', '12N345');
439             my $partno = unfmt('@"..^.+"', $partseq);
440              
441             would return "12 345", since the original format IGNORED the third character
442             "N" in the original string. Since this is unknown, unfmt() interprets "^" as
443             insert a space character. Careful use of unfmt() can often produce desired
444             results. For example:
445              
446             $s = fmt('@$,10.2> CR', '-1234567.89');
447             print "-s4 formatted=$s=\n"; # $s =" $1,234,567.89 CR"
448             $s = unfmt('@$,10.2> CR', $s);
449             print "-s4 unformatted=$s=\n"; # $s ="-1234567.89" (The original number)
450              
451             =item <$integer> = B(I);
452              
453             Returns the format "size" represented by the , just like the
454             second element of the array returned by B() in array context, see above.
455             If a maximum length specifier is given, it returns that. Otherwise, attempts
456             to determine the length of the data string that would be returned by applying
457             the format. For "wrap" formats, this is the length of a single row. For
458             regular expressions and user-supplied functions, it is zero (indeterminate).
459              
460             =item <$character> = B(I);
461              
462             Returns a character indicating the justification (if any) represented by the
463             specified , just like the third element of the array returned
464             by B() in array context, see above. The result can be either "<", ">",
465             "|", or "", if not determinable.
466              
467             =item <$integer> = B(I, I [, I ]);
468              
469             Returns the "suffix" string, if any, included in the .
470              
471             =back
472              
473             =head1 KEYWORDS
474              
475             formatting, picture_clause, strings
476              
477             =cut
478              
479             package String::PictureFormat;
480              
481 2     2   52747 use strict;
  2         6  
  2         74  
482             #use warnings;
483 2     2   11 use vars qw(@ISA @EXPORT $VERSION);
  2         4  
  2         156  
484             $VERSION = '1.01';
485              
486 2     2   15925 use Time::Local;
  2         3823  
  2         33072  
487              
488             require Exporter;
489              
490             my $haveTime2fmtstr = 0;
491             my $haveFmtstr2time = 0;
492             #eval 'require "to_char.pl"; $haveTime2fmtstr = 1; 1';
493              
494             @ISA = qw(Exporter);
495             @EXPORT = qw(fmt fmtsiz fmtjust fmtsuffix unfmt);
496              
497             sub fmt {
498 8     8 1 4098 my $pic = shift;
499 8         17 my $v = shift;
500 8         11 my $ops = shift;
501              
502 8         14 my $leni = 0;
503 8         12 my $suffix;
504 8 50       24 my $errchar = $ops->{'-bad'} ? substr($ops->{'-bad'},0,1) : '*';
505 8 100       43 my $justify = ($pic =~ /^.*?([<|>])/o) ? $1 : '';
506 8 50       28 my $fixedLeni = $ops->{-sizefixed} ? fmtsiz($pic) : 0;
507 8 100       41 if ($pic =~ s/^\@//o) { #@-strings:
    50          
    0          
508 7 50       23 $leni = $1 if ($pic =~ s/^(\d+)\://o);
509 7 50       15 $leni = $fixedLeni if ($fixedLeni);
510 7 100       51 if ($pic =~ s#^([\'\"\/\`])##o) { #PICTURE LITERAL (@'foo'
    100          
    100          
    100          
511 1         3 my $regexDelimiter = $1; #REPLACE EACH DOT WITH NEXT CHAR. SKIP ONES CORRESPONDING WITH "^", ALL OTHER CHARS ARE LITERAL.
512 1 50       26 $suffix = ($pic =~ s#\Q$regexDelimiter\E(.*)$##) ? $1 : '';
513 1         3 my $cnt = 0; #EXAMPLE: fmt("@\"...-..-.+\";suffix", '123456789'); FORMATS AN SSN:
514 1         2 my $frompic = '';
515 1         2 my $graball = 0;
516 1         2 my $charsHandled = 0; #NO. OF CHARS IN THE INPUT STRING THAT CAN BE OUTPUT.
517 1         2 $pic =~ s/\\\+/\x02/go;
518 1         3 $pic =~ s/\\\./\x03/go;
519 1         2 $pic =~ s/\\\^/\x04/go;
520 1         2 my $t = $pic;
521 1         6 while ($t =~ s/\^//o) {
522 0         0 $charsHandled++;
523             }
524 1         6 $pic =~ s/([\.]+[+*?]?|[\^]+)/
525 3         7 my $one = $1;
526 3 50       8 if ($one =~ s!\^!\.!go)
527             {
528 0         0 $frompic .= $one;
529 0         0 ''
530             }
531             else
532             {
533 3         8 my $catcher = '('.$1.')';
534 3 50       9 $graball = 1 if ($one =~ m#\+$#o);
535 3         4 $frompic .= $catcher;
536 3         4 ++$cnt;
537 3         12 '$'.$cnt
538             }
539             /eg;
540 1         5 my $evalstr = '$v =~ s"'.$frompic.'"'.$pic.'"';
541 1 50       3 if ($graball) {
542 0         0 $charsHandled = length($v);
543             } else {
544 1         2 my $l = 0;
545 1         2 $t = $frompic;
546 1         7 while ($t =~ s/\((\.+)\)//o) {
547 3         13 $l += length($1);
548             }
549 1         2 $charsHandled += $l;
550 1 50       3 unless ($leni) {
551 1         6 ($t = $pic) =~ s/\$\d+//og;
552 1         2 $l += length($t);
553 1         3 $leni = $l;
554             }
555             }
556 1         18 my $v0 = $v;
557 1         112 eval $evalstr;
558 1         4 $v =~ s/\x04/\^/go;
559 1         3 $v =~ s/\x03/\./go;
560 1         2 $v =~ s/\x02/\+/go;
561 1 50 33     13 if ((length($v0) > $charsHandled || ($leni > 0 && length($v) > $leni)) && $ops->{'-truncate'} !~ /no/io) {
      33        
562 0 0       0 $v = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni : substr($v, 0, $leni);
563             }
564 1 50       5 $v .= $suffix unless ($ops->{'-suffix'} =~ /no/io);
565 1 50       7 return wantarray ? ($v, $leni, $justify) : $v;
566             } elsif ($pic =~ s#^\^##o) { #DATE-CONVERSION
567 1 50   1   76 eval 'use Date::Time2fmtstr; $haveTime2fmtstr = 1; 1' unless ($haveTime2fmtstr);
  1         317  
  0            
  0            
568 1 50       9 $suffix = ($pic =~ s#\^(.*)$##) ? $1 : '';
569 1         2 my $t;
570 1 50       6 my $perltime = ($v =~ /^\d{9,11}$/o) ? $v : 0;
571 1 50       5 unless ($perltime) { #WE HAVE A DATE STRING, IE. yyyy-dd-mm, etc. THAT CHKDATE CAN HANDLE:
572 1         5 ($t, $perltime) = _chkdate($v);
573 1 50 33     20 unless ($t || $perltime) {
574 0   0     0 $leni ||= $fixedLeni || length($pic);
      0        
575 0         0 $v = $errchar x $leni;
576 0 0       0 return wantarray ? ($v, $leni, $justify) : $v;
577             }
578 1 50       5 if ($haveTime2fmtstr) {
    50          
579 0   0     0 $v = $perltime || &timelocal(0,0,0,substr($t,6,2),
580             (substr($t,4,2)-1),substr($t,0,4),0,0,0);
581             } elsif ($perltime) {
582 1         2 $v = $perltime;
583             }
584             }
585 1 50       6 if ($v =~ /^\d{8}\d+$/o) { #WE HAVE A PERL "TIME":
586 1 50       4 if ($haveTime2fmtstr) { #WE ALSO HAVE Time2fmtstr!:
587 0         0 $t = time2str($v, $pic);
588 0   0     0 $leni ||= $fixedLeni || length($t);
      0        
589 0 0 0     0 if ($leni && length($t) > $leni && $ops->{'-truncate'} !~ /no/io) {
      0        
590 0 0       0 $v = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni
591             : substr($t, 0, $leni);
592             } else {
593 0         0 $v = $t;
594             }
595 0 0       0 $v .= $suffix unless ($ops->{'-suffix'} =~ /no/io);
596 0 0       0 return wantarray ? ($v, $leni, $justify) : $v;
597             } else { #NO Time2fmtstr, SO WE'LL CONVERT PERL "TIME" TO "yyyymmdd hhmmss" FOR MANUAL CONVERSION:
598 1         17 my @tv = localtime($v); #NOTE: MANUAL CONVERSION DOESN'T HANDLE ALL THE FORMAT PICTURES THAT Time2fmtstr DOES!:
599 1         11 $t = sprintf('%4.4d',$tv[5]+1900) . sprintf('%2.2d',$tv[4]+1) . sprintf('%2.2d',$tv[3])
600             . ' ' . sprintf('%2.2d',$tv[2]) . sprintf('%2.2d',$tv[1]) . sprintf('%2.2d',$tv[0]);
601             }
602             }
603 1 50       6 if ($t =~ /^\d{8}(?: \d{4,6})?$/o) { #WE HAVE A STRING WE CAN TRY TO CONVERT MANUALLY:
604 1         4 $pic =~ s/yyyy/substr($t,0,4)/ie;
  1         4  
605 1         3 $pic =~ s/yy/substr($t,2,4)/ie;
  0         0  
606 1         4 $pic =~ s/mm/substr($t,4,2)/ie;
  1         3  
607 1         4 $pic =~ s/dd/substr($t,6,2)/ie;
  1         3  
608 1         3 $pic =~ s/hh/substr($t,9,2)/ie;
  1         3  
609 1         2 $pic =~ s/mi/substr($t,11,2)/ie;
  1         9  
610 1         4 $pic =~ s/ss/substr($t,13,2)/ie;
  1         2  
611 1         3 $v = $pic;
612 1   33     9 $leni ||= $fixedLeni || length($v);
      33        
613 1 50 33     16 if ($leni && length($t) > $leni && $ops->{'-truncate'} !~ /no/io) {
      33        
614 0 0       0 $v = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $fixedLeni
615             : substr($t, 0, $fixedLeni);
616             }
617 1 50       4 $v .= $suffix unless ($ops->{'-suffix'} =~ /no/io);
618 1 50       6 return wantarray ? ($v, $leni, $justify) : $v;
619             } else {
620 0   0     0 $leni ||= $fixedLeni || length($pic);
      0        
621 0         0 $v = $errchar x $leni;
622 0 0       0 return wantarray ? ($v, $leni, $justify) : $v;
623             }
624             } elsif ($pic =~ m#^(?:s|tr)(\W)#) { #REGEX SUBSTITUTION (@s/foo/bar/)
625 1         3 my $regexDelimiter = $1;
626 1 50       24 $suffix = ($pic =~ s#([^$regexDelimiter]+)$##) ? $1 : '';
627 1 50       6 my $regexPostOp = ($suffix =~ s/^(\w+)\;//) ? $1 : '';
628 1         3 my $evalstr = '$v =~ '.$pic.$regexPostOp;
629 1         80 eval $evalstr;
630 1 0 33     8 if ($leni && length($v) > $leni && $ops->{'-truncate'} !~ /no/io) {
      33        
631 0 0       0 $v = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni : substr($v, 0, $leni);
632             }
633 1 50       6 $v .= $suffix unless ($ops->{'-suffix'} =~ /no/io);
634 1 50       7 return wantarray ? ($v, $leni, $justify) : $v;
635             } elsif ($pic =~ /^[a-zA-Z_]+/o) { #USER-SUPPLIED FUNCTION (@foo('*'))
636 1 50       8 $suffix = ($pic =~ s/\)([^\)]*)$/\)/) ? $1 : '';
637 1         3 $pic =~ s/\\\*/\x02/og;
638 1         3 $pic =~ s/\\\#/\x03/og;
639 1         2 $pic =~ s/\\\(/\x04/og;
640 1         3 $pic =~ s/\\\)/\x05/og;
641 1         4 $pic =~ s/\(\s*\)/\(\*\,\#\)/o;
642 1 50       5 if ($v =~ /^\d+$/o)
643             {
644 0         0 $pic =~ s/\*/$v/g;
645             }
646             else
647             {
648 1         8 $pic =~ s/\*/\'$v\'/g;
649             }
650 1         5 $pic =~ s/\#/$leni/g;
651 1         2 $pic =~ s/\x05/\)/og;
652 1         3 $pic =~ s/\x04/\(/og;
653 1         3 $pic =~ s/\x03/\#/og;
654 1         2 $pic =~ s/\x02/\*/og;
655 1 50       5 $pic = 'main::' . $pic unless ($pic =~ /^\w+\:\:/o);
656 1         2 my $t;
657 1 50       6 $pic =~ s/(\w)(\W*)$/$1\(\'$v\',$leni\)$2/ unless ($pic =~ /\(.*\)/o);
658 1         73 eval "\$t = $pic";
659 1 50       17 $t = $@ if ($@);
660 1 0 33     5 if ($leni && length($t) > $leni && $ops->{'-truncate'} !~ /no/io) {
      33        
661 0 0       0 $t = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni : substr($t, 0, $leni);
662             }
663 1 50       6 $t .= $suffix unless ($ops->{'-suffix'} =~ /no/io);
664 1 50       8 return wantarray ? ($t, $leni, $justify) : $t;
665             } else { #REGULAR STUFF, IE. @12>.>>)
666 3         5 my $leniSpecified = $leni;
667 3 50       11 if ($pic =~ /^\*(.*)$/)
668             {
669 0         0 $suffix = $1;
670 0 0 0     0 if ($leni && length($v) > $leni && $ops->{'-truncate'} !~ /no/io) {
      0        
671 0 0       0 $v = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni : substr($v, 0, $leni);
672             }
673 0 0       0 $v .= $1 unless ($ops->{'-suffix'} =~ /no/io);
674 0 0       0 return wantarray ? ($v, 0, '<') : $v;
675             }
676 3 100       20 $suffix = ($pic =~ s/([^\<\|\>\.\^]+)$//o) ? $1 : '';
677 3         3 my ($special, $float, $t);
678 3         6 my $commatize = 0;
679 3         14 while ($pic =~ s/^([^\d\<\|\>\.\^])//o) { #STRIP OFF ALL CHARS BEFORE <, >, |, OR DIGIT AS "FLOATING CHARS".
680 7         11 $special = $1;
681 7 100       15 if ($special eq ',') { #COMMA (@,) = ADD COMMAS EVERY 3 DIGITS:
682 3 50       14 $commatize = 1 unless ($ops->{'-nonnumeric'});
683             } else {
684 4         17 $float .= $special; #OTHERS, IE. (@$) ARE FLOATERS:
685             }
686             }
687 3 50       8 my $switchFloat = ($float =~ /\+\$/o) ? 1 : 0;
688 3 100       9 if ($float =~ /\(/o) #ONLY KEEP FLOATING "(" IF SUFFIX STARTS WITH A ")"!
689             {
690 1 50       6 $float =~ s/\(//o unless ($suffix =~ /^\)/o);
691             }
692 3 100       7 if ($v < 0)
693             {
694 2         5 $float =~ s/\+//go; #REMOVE FLOATING "+" IF VALUE IS NEGATIVE.
695 2 50 33     15 $leni = 1 + length($float) unless ($fixedLeni || $leniSpecified); #COUNT FLOATING CHARS IN FIELD SIZE:
696             }
697             else
698             {
699 1 50 33     8 $leni = 1 + length($float) unless ($fixedLeni || $leniSpecified); #COUNT FLOATING CHARS IN FIELD SIZE:
700 1         3 $float =~ s/\-//o;
701 1 50 33     12 $leni++ if (!($fixedLeni || $leniSpecified) && $float =~ s/\(//o); #REMOVE FLOATING "(..)" IF VALUE IS NOT NEGATIVE.
      33        
702             }
703 3         16 $pic =~ s/(\d+)[<|>]?([\.\^]?)(\d*)([<|>])/
704 3         10 my ($one, $dec, $two, $three) = ($1, $2, $3, $4);
705 3   50     15 $dec ||= '.';
706 3         12 my $exp = ($three x $one);
707 3 50       12 $exp .= $dec . ($three x $two) if ($two > 0);
708 3         10 $exp
709             /e; #CONVERT STUFF LIKE "@12.2>" TO "@12>.2>".
710             #DEFAULT JUSTIFY: RIGHT IF COMMATIZING(NUMBER) OR FLOATING$ OR PICTURE CONTAINS DECIMAL;
711             #OTHERWISE, DEFAULT IS LEFT.
712 3 0 0     9 $justify ||= ($commatize || $float =~ /\$/o || $pic =~ /[\.\,\^\$]/o) ? '>' : '<';
      33        
713             #CALCULATE FIELD SIZE BASED ON NO. OF "<, >, |" AND PRECEEDING REPEATER DIGITS:
714 3 50 33     19 unless ($fixedLeni || $leniSpecified)
715             {
716 3         5 $leni += length($pic); # && $pic =~ /([<|>\.]+)/o);
717             }
718 3         15 my ($wholePic, $decPic) = split(/[\.\^]/o, $pic);
719 3         5 my $decLeni = 0;
720 3         5 my $wholeLeni = $leni;
721 3         6 my $decJustify = $justify;
722 3 50 33     32 if ($decPic && !$ops->{'-nonnumeric'}) { #PICTURE CONTAINS A DECIMAL, CALCULATE SEPARATE LENGTHS, ETC.
723 3         6 $decLeni = 0;
724 3         5 $t = $decPic;
725 3         20 $decLeni += length($1) while ($t =~ s/([\<\|\>\.\^\,\$]+)//o);
726 3         9 $decLeni += $1 - 1 while ($t =~ s/(\d+)//o);
727 3 50       12 $decJustify = $1 if ($decPic =~ /([\<\|\>])$/o);
728 3         5 $wholeLeni = $leni - ($decLeni + 1);
729 3 0 33     10 if ($pic !~ /\./o && $v !~ /\./) { #WE HAVE AN "IMPLIED DECIMAL POINT!
730 0 0       0 $v = sprintf("%.${decLeni}f", $v / (10**$decLeni)) if ($v =~ /^[\+\-\d\. ]+$/o);
731             }
732 3         10 my ($whole, $decimal) = split(/\./o, $v); #SPLIT THE VALUE TOO:
733 3 50       15 unless ($float =~ /\+/o) {
734 3 100 100     20 $whole =~ s/^-//o if ($v >= 0 || $suffix =~ /^[\_ ]*CR\s*$/io)
735             }
736 3         4 my $l = length($whole);
737 3   33     11 while ($l > $wholeLeni && $float && $float ne '(') { #FIRST REMOVE FLOAT CHARACTERS IF WON'T FIT:
      33        
738 0 0       0 --$l if ($float =~ s/.(\(?)$/$1/);
739             }
740 3         8 $t = $whole . '.' . $decimal;
741 3 50       8 if ($decJustify eq '>') { #CHOP RIGHT-MOST DECIMAL PLACES AS NEEDED TO FIT IFF DECIMAL PART IS "RIGHT-JUSTIFIED"
742 3   33     9 while (length($t) > $leni && $t =~ /\./o) { #NOTE:WE DON'T "JUSTIFY" THE DECIMAL PART!
743 0         0 chop $t;
744 0         0 $decLeni--;
745             }
746             }
747 3 50       7 $decLeni = 0 if ($decLeni < 0);
748 3         9 $pic = '%.'.$decLeni.'f'; #BUILD SPRINTF TO ADD/ROUND DECIMAL PLACES.
749 3         33 $t = sprintf($pic, $v); #JUST THE # W/PROPER # OF DECIMAL PLACES.
750             } else {
751 0         0 $t = $v;
752 0         0 my $l = length($v);
753 0 0       0 unless ($ops->{'-nonnumeric'}) {
754 0   0     0 while ($l > $leni && $float) { #FIRST REMOVE FLOAT CHARACTERS IF WON'T FIT:
755 0         0 chop($float);
756 0         0 --$l;
757             }
758 0   0     0 while (length($t) > $leni && $t =~ /\./o) {
759 0         0 chop $t;
760             }
761             }
762             }
763 3 50       7 unless ($ops->{'-nonnumeric'})
764             {
765 3 100       8 if ($v >= 0) #SPECIAL SUFFIX "CR" OR " CR": REMOVE IF VALUE >= 0:
766             {
767 1         2 $suffix =~ s/^([\_ ]*)CR\s*$/' 'x(length($1)+2)/ei;
  0         0  
768             }
769             else #INCLUDE SPECIAL SUFFIX "CR" OR "_CR" IF VALUE < 0 FOR ACCOUNTING:
770             {
771 2 100       9 $t =~ s/\-//o if ($suffix =~ s/^([\_ ]*)(CR\s*)$/(' 'x(length($1))).$2/ei);
  1         8  
772             }
773             }
774 3 100       13 $t =~ s/^\-//o if ($float =~ /[\(\-]/o);
775 3         4 my $l = length($t);
776 3         4 my $t2;
777 3   66     16 while ($l < $leni && $float) { #DIDN'T SPLIT ON ".", SO ONLY ADD FLOAT CHARS IF WILL STILL FIT:
778 4         8 $t2 = chop($float);
779 4 50 66     38 unless (!$ops->{'-nonnumeric'} && $t2 eq '(' && $v >= 0) {
      66        
780 4         7 $t = $t2 . $t;
781 4         20 ++$l;
782             }
783             }
784 3 0 33     10 $t =~ s/^[^ \d\<\|\>\.]([ \d\.\-\+]+)$/\($1/ if ($l == $leni && $v < 0 && $float =~ s/\(//o && !$ops->{'-nonnumeric'});
      33        
      0        
785 3 50       6 if ($commatize) { #ADD COMMAS TO LARGE NUMBERS, IF WILL FIT:
786 3         5 $l = length($t);
787 3 50       7 if ($decJustify eq '>') {
788 3   33     10 while ($l > $leni && $t =~ /\./o) {
789 0         0 chop $t;
790             }
791             }
792 3   33     35 while ((!$leniSpecified || $l < $leni) && $t =~ s/(\d)(\d\d\d)\b/$1,$2/) {
      66        
793 6         8 $l = length($t);
794 6 50 33     76 $leni++ unless ($fixedLeni || $leniSpecified);
795             }
796             }
797 3 50       7 $t =~ s/\$\-/\-\$/o if ($switchFloat);
798 3 50 33     19 if ($ops->{'-truncate'} =~ /er/io && length($t) > $leni) {
    50 33        
799 0         0 $v = $errchar x $leni;
800             } elsif ($ops->{'-truncate'} !~ /no/io || length($t) <= $leni) {
801 3 50 33     30 $leni-- if (!($fixedLeni || $leniSpecified) && $float =~ /\(/o);
      33        
802 3 50       11 if ($justify eq '|') { #JUSTIFY:
    50          
803 0         0 my $j = int(($leni - $l) / 2);
804 0         0 $v = sprintf("%-${leni}s", (' ' x $j . $t));
805 0 0       0 return wantarray ? ($v, $leni, $justify) : $v;
806             } elsif ($justify eq '<') {
807 0         0 $v = sprintf("%-${leni}s", $t);
808             } else {
809 3         11 $v = sprintf("%${leni}s", $t);
810             }
811             } else {
812 0 0 0     0 $leni-- if (!($fixedLeni || $leniSpecified) && $float =~ /\(/o);
      0        
813 0         0 $v = $t;
814             }
815 3 100       11 $suffix =~ s/^\)/ /o unless ($v =~ /\(/o);
816 3 50       10 $v .= $suffix unless ($ops->{'-suffix'} =~ /no/io);
817 3 50       16 return wantarray ? ($v, $leni, $justify) : $v;
818             }
819             } elsif ($pic =~ s/^\=//o) { #FIELDS STARTING WITH "=" ARE TO BE WRAPPED TO MULTIPLE LINES AS NEEDED:
820 1 50       3 $leni = $fixedLeni if ($fixedLeni);
821 1         3 my ($justify, $wrapchar) = ('<', 'W'); #DEFAULTS.
822 1         2 my $j = 1;
823 1 50       4 $suffix = ($pic =~ s/([^wW<|>\d]+)$//o) ? $1 : '';
824 1 50       4 $wrapchar = 'w' if ($pic =~ /w/o); #LITTLE w=WRAP AT CHARACTER:
825 1 50       12 $justify = $1 if ($pic =~ /^.*([<|>])/o); #BIG W=WRAP AT WORD BOUNDARIES (Text::Wrap):
826 1         10 $j += length($1) while ($pic =~ s/([wW<|>]+)//o);
827 1         8 $j += $1 - 1 while ($pic =~ s/(\d+)//o);
828 1 50       12 $leni = $j unless ($fixedLeni); #WIDTH OF FIELD AREA TO WRAP WITHIN:
829 1         2 my $mylines = 0;
830 1         2 my $t;
831 1 50       4 if (length $pic) {
832 0 0       0 $suffix = ($ops->{'-suffix'} !~ /no/io) ? $pic . $suffix : $pic;
833 0         0 $pic = '';
834             }
835 1         3 my $suffixPadding = ' ' x length($suffix);
836 1 50       3 if ($wrapchar eq 'W') { #WRAP BY WORD (Text::Wrap):
837 1         1644 require Text::Wrap; Text::Wrap->import( qw(wrap) );
  1         3772  
838             #no warnings;
839 1         2 $Text::Wrap::columns = $leni + 1;
840             #use warnings;
841 1         3 eval {$t = wrap('','',$v);};
  1         4  
842 1 50       421 if ($@) {
843 0         0 $wrapchar = 'w'; #WRAP CRAPPED :-(, DO MANUALLY (BY CHARACTER)!
844             } else {
845 1         5 my @fli = split(/\n/o, $t); #@fli ELEMENTS EACH REPRESENT A LINE:
846 1 50       5 if ($justify eq '>') { #JUSTIFY:
    50          
847 0         0 for (my $i=0;$i<=$#fli;$i++) {
848 0         0 $fli[$i] = sprintf("%${leni}s", $fli[$i]);
849 0 0       0 unless ($ops->{'-suffix'} =~ /no/io) {
850 0 0 0     0 $fli[$i] .= (!$i || $ops->{'-suffix'} =~ /all/io)
851             ? $suffix : $suffixPadding
852             }
853             }
854             } elsif ($justify eq '|') {
855 0         0 my $l;
856 0         0 for (my $i=0;$i<=$#fli;$i++) {
857 0         0 $l = length($fli[$i]);
858 0         0 $j = int(($leni - $l) / 2);
859 0         0 $fli[$i] = sprintf("%${leni}s", ($fli[$i] . ' 'x$j));
860 0 0       0 unless ($ops->{'-suffix'} =~ /no/io) {
861 0 0 0     0 $fli[$i] .= (!$i || $ops->{'-suffix'} =~ /all/io)
862             ? $suffix : $suffixPadding
863             }
864             }
865             } else {
866 1         2 my $l;
867 1         6 for (my $i=0;$i<=$#fli;$i++) {
868 4         5 $l = length($fli[$i]);
869 4         9 $j = int(($leni - $l) / 2);
870 4         14 $fli[$i] = sprintf("%-${leni}s", $fli[$i]);
871 4 50       11 unless ($ops->{'-suffix'} =~ /no/io) {
872 4 100 66     27 $fli[$i] .= (!$i || $ops->{'-suffix'} =~ /all/io)
873             ? $suffix : $suffixPadding
874             }
875             }
876             }
877 1         4 $t = join("\n", @fli); #CAN RETURN #LINES AS 2ND ELEMENT:
878 1 50       7 return wantarray ? (\@fli, $leni, $justify, scalar(@fli)) : \@fli;
879             }
880             }
881 0 0       0 if ($wrapchar eq 'w') { #WRAP BY CHARACTER (WORDS MAY BE SPLIT):
882 0         0 $j = 0;
883 0         0 my $l = length($v);
884 0         0 my @fli = ();
885 0         0 while ($j < $l)
886             {
887 0         0 push (@fli, substr($v,$j,$leni));
888 0         0 $mylines += 1;
889 0 0       0 unless ($ops->{'-suffix'} =~ /no/io) {
890 0 0 0     0 $fli[$#fli] .= (!$j || $ops->{'-suffix'} =~ /all/io)
891             ? $suffix : $suffixPadding
892             }
893 0         0 $j += $leni;
894             }
895 0 0       0 if ($justify eq '>') {
    0          
896 0         0 $fli[$#fli] = sprintf("%${leni}s", $fli[$#fli]);
897             } elsif ($justify eq '|') {
898 0         0 $l = length($fli[$#fli]);
899 0         0 $j = int(($leni - $l) / 2);
900 0         0 $fli[$#fli] = sprintf("%${leni}s", ($fli[$#fli] . ' 'x$j));
901             } else {
902 0         0 $fli[$#fli] = sprintf("%-${leni}s", $fli[$#fli]);
903             }
904 0 0       0 return wantarray ? (\@fli, $leni, $justify, scalar(@fli)) : \@fli;
905             }
906             } elsif ($pic =~ s/^\%//o) { #C-PRINTF FORMAT STRINGS (%-STRINGS) (AS-IS, "%" NOT INCLUDED IN FIELD SIZE):
907 0 0       0 $leni = $fixedLeni if ($fixedLeni);
908 0 0       0 my $float = ($pic =~ s/^\$//o) ? '$' : ''; #EXCEPTION: FLOATING $, COMMA(COMMATIZE) ALLOWED AFTER "%":
909 0 0       0 my $commatize = ($pic =~ s/^\,//o) ? 1 : 0; #IE: "%$,-14.2f": FIELD SIZE=16!
910 0 0       0 $suffix = ($pic =~ s/^(\-?[\d\.]+\w)(.*)$/$1/o) ? $2 : '';
911 0 0       0 $leni = ($pic =~ /^\-?(\d+)/) ? $1 : length($v) unless ($fixedLeni);
    0          
912 0 0       0 my $lj = ($pic =~ /^\-/o) ? '-' : '';
913 0 0       0 $justify = ($lj eq '-') ? '<' : '>';
914 0         0 $pic = '%' . $pic;
915 0         0 my $t;
916 0 0       0 my $decimal = ($pic =~ /\.(\d+)/o) ? $1 : 0;
917 0 0       0 if ($float) {
918 0         0 $lj = '';
919 0 0       0 $lj = '-' if ($pic =~ s/^\%\-/\%/o);
920 0 0       0 unless ($fixedLeni) {
921 0 0       0 $leni += length($float) if ($pic =~ /^\%(\d+)/o);
922             }
923 0         0 $v = sprintf("%.${decimal}f", $v);
924             }
925 0         0 my $l;
926 0 0       0 if ($commatize) {
927 0 0       0 unless ($fixedLeni) {
928 0 0       0 $leni++ if ($pic =~ /^\%(\d+)/o);
929             }
930 0         0 $l = length($v);
931 0   0     0 while ($l > $leni && $v =~ /\./o) {
932 0         0 chop $v;
933             }
934 0 0       0 if ($l > $leni) {
935 0         0 $v = $errchar x $leni;
936 0 0       0 return wantarray ? ($v, $leni, $justify) : $v;
937             }
938 0   0     0 while ($l < $leni && $v =~ s/(\d)(\d\d\d)\b/$1,$2/) {
939 0         0 $l = length($v);
940             }
941             } else {
942 0 0       0 $v = sprintf($pic, $v) unless ($float);
943 0         0 $l = length($v);
944             }
945 0 0 0     0 $v = $float . $v if ($float && $l < $leni);
946 0         0 $v = sprintf("%${lj}${leni}.${leni}s", $v);
947 0 0       0 $v .= $suffix unless ($ops->{'-suffix'} =~ /no/io);
948 0 0       0 return wantarray ? ($v, $leni, $justify) : $v;
949             } else {
950 0         0 return undef; #INVALID PICTURE STRING:
951             }
952             }
953              
954             sub unfmt {
955 1     1 1 617 my $pic = shift;
956 1         2 my $v = shift;
957 1         2 my $ops = shift;
958              
959 1         2 my $leni = 0;
960 1         3 my $leniSpecified = 0;
961 1         2 my $suffix;
962 1 50       13 my $errchar = $ops->{'-bad'} ? substr($ops->{'-bad'},0,1) : '*';
963 1 50       6 my $justify = ($pic =~ /^.*?([<|>])/o) ? $1 : '';
964 1 50       5 my $fixedLeni = $ops->{-sizefixed} ? fmtsiz($pic) : 0;
965 1 50       7 if ($pic =~ s/^\@//o) { #@-strings:
    0          
    0          
966 1 50       3 $leni = $fixedLeni if ($fixedLeni);
967 1 50       4 $leni = $1 if ($pic =~ s/^(\d+)\://o);
968 1         1 $leniSpecified = $leni;
969 1 50       6 if ($pic =~ s/^([\'\"\/\`])//o) { #PICTURE LITERAL (@'foo'
    0          
    0          
    0          
970 1         2 my $regexDelimiter = $1; #REPLACE EACH DOT WITH NEXT CHAR. SKIP ONES CORRESPONDING WITH "^", ALL OTHER CHARS ARE LITERAL.
971 1 50       24 $v =~ s/$1$// if ($pic =~ s#\Q$regexDelimiter\E(.*)$##);
972 1         3 my $r0 = $pic;
973 1         3 $r0 =~ s/\\.//gso;
974 1         15 $r0 =~ s/(\.+[\+\*]*)/\($1\)/gs;
975 1         2 my $r = $r0;
976 1         2 $r0 =~ s/\^//gso;
977 1         2 my @QS;
978 1         2 my $i = 0;
979 1         4 $i++ while ($r0 =~ s/(\([^\)]+\))/
980 3         9 $QS[$i] = "$1"; "P$i"/e);
  3         16  
981              
982 1         3 $r0 = "\Q$r0\E";
983 1         8 $r0 =~ s/P(\d+)/$QS[$1]/gs;
984 1         2 $i = 1;
985 1         17 $i++ while ($r =~ s/\(.+?\)/\$$i/s);
986 1         2 $r =~ s/\^/ /gso;
987 1         5 $r =~ s/[^\$\d ]//gso;
988 1         4 my $evalstr = "\$v =~ s\"$r0\"$r\"";
989 1         103 eval $evalstr;
990 1 0 33     21 if ($leni && length($v) > $leni && $ops->{'-truncate'} !~ /no/io) {
      33        
991 0 0       0 $v = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni : substr($v, 0, $leni);
992             }
993 1 50       10 return wantarray ? ($v, $leni, $justify) : $v;
994             } elsif ($pic =~ s#^\^##o) { #DATE-CONVERSION
995 0 0       0 eval 'use Date::Fmtstr2time; $haveFmtstr2time = 1; 1' unless ($haveFmtstr2time);
996 0         0 my $t = '';
997 0 0       0 if ($haveFmtstr2time) { #CONVERT TO A PERL "TIME" USING Fmtstr2time IF IT'S AVAILABLE:
998 0         0 $t = str2time($v, $pic);
999 0 0 0     0 if ($t =~ /^\d+$/o && $ops->{'-outfmt'}) { #WE WANT THE TIME FORMATTED TO A STRING:
1000 0 0       0 eval 'use Date::Time2fmtstr; $haveTime2fmtstr = 1; 1' unless ($haveTime2fmtstr);
1001 0 0       0 $t = time2str($t, $ops->{'-outfmt'}) if ($haveTime2fmtstr);
1002             }
1003             } else { #ATTEMPT A MANUAL TRANSLATION TO AN INTEGER FORMATTED: yyyymmdd[hhmm[ss]]
1004 0         0 foreach my $i (qw(yyyy mm dd)) {
1005 0   0     0 $t .= substr($v,index($pic,$i),length($i)) || ' ' x length($i);
1006             }
1007 0         0 $t =~ s/^ /'20'.substr($v,index($pic,'yy'),2)/e;
  0         0  
1008 0         0 $t =~ s/ $/01/;
1009 0         0 $t =~ s/ /$errchar/g;
1010 0         0 foreach my $i (qw(HH hh mi ss)) {
1011 0 0       0 $t .= substr($v,index($pic,$i),length($i)) if (index($pic,$i) > 0);
1012             }
1013 0         0 $t =~ s/[^0-9 ]/ /go;
1014             }
1015 0 0 0     0 if ($leni && length($t) > $leni && $ops->{'-truncate'} !~ /no/io) {
      0        
1016 0 0       0 $t = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni : substr($t, 0, $leni);
1017             }
1018 0 0       0 return wantarray ? ($t, $leni, $justify) : $t;
1019             } elsif ($pic =~ m#^(?:s|tr)(\W)#) { #REGEX SUBSTITUTION (@s/foo/bar/) #NOTE: UNFMT=FMT!!!
1020 0         0 my $regexDelimiter = $2;
1021 0 0       0 $v =~ s/$1$// if ($pic =~ s#\Q$regexDelimiter\E(.*)$##);
1022 0         0 my $evalstr = '$v =~ '.$pic;
1023 0         0 eval $evalstr;
1024 0 0 0     0 if ($leni && length($v) > $leni && $ops->{'-truncate'} !~ /no/io) {
      0        
1025 0 0       0 $v = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni : substr($v, 0, $leni);
1026             }
1027 0 0       0 return wantarray ? ($v, $leni, $justify) : $v;
1028             } elsif ($pic =~ /^[a-zA-Z_]+/o) { #USER-SUPPLIED FUNCTION (@foo('*')) #NOTE: UNFMT=FMT!!!
1029 0 0       0 $v =~ s/$1$// if ($pic =~ s#\Q\;\E(.*)$##);
1030 0         0 $pic =~ s/\\\*/\x02/og;
1031 0         0 $pic =~ s/\\\*/\x02/og;
1032 0         0 $pic =~ s/\\\#/\x03/og;
1033 0 0       0 if ($v =~ /^\d+$/o)
1034             {
1035 0         0 $pic =~ s/\*/$v/g;
1036             }
1037             else
1038             {
1039 0         0 $pic =~ s/\*/\'$v\'/g;
1040             }
1041 0         0 $pic =~ s/\#/$leni/g;
1042 0         0 $pic =~ s/\x03/\#/og;
1043 0         0 $pic =~ s/\x02/\*/og;
1044 0 0       0 $pic = 'main::' . $pic unless ($pic =~ /^\w+\:\:/o);
1045 0         0 my $t;
1046 0 0       0 $pic =~ s/(\w)(\W*)$/$1\(\'$v\',$leni\)$2/ unless ($pic =~ /\(.*\)/o);
1047 0         0 eval "\$t = $pic";
1048 0 0       0 $t = $@ if ($@);
1049             #NO! $t .= $suffix unless ($ops->{'-suffix'} =~ /no/io);
1050 0 0 0     0 if ($leni && length($v) > $leni && $ops->{'-truncate'} !~ /no/io) {
      0        
1051 0 0       0 $v = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni : substr($v, 0, $leni);
1052             }
1053 0 0       0 return wantarray ? ($t, $leni, $justify) : $t;
1054             } else { #REGULAR STUFF, IE. @12>.>>)
1055 0 0       0 if ($pic =~ /^\*(.*)$/)
1056             {
1057 0         0 $suffix = $1;
1058 0 0 0     0 if ($leni && length($v) > $leni && $ops->{'-truncate'} !~ /no/io) {
      0        
1059 0 0       0 $v = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni : substr($v, 0, $leni);
1060             }
1061 0 0       0 $v .= $1 unless ($ops->{'-suffix'} =~ /no/io);
1062 0 0       0 return wantarray ? ($v, 0, '<') : $v;
1063             }
1064 0 0       0 $suffix = $1 if ($pic =~ s/([^<|>.]+)$//o);
1065 0         0 my ($special, $isneg, $t);
1066 0         0 my $commatize = 0;
1067 0         0 while ($pic =~ s/^([^\d\<\|\>\.])//o) { #STRIP OFF ALL CHARS BEFORE <, >, |, OR DIGIT AS "FLOATING CHARS".
1068 0         0 $special .= $1;
1069             }
1070 0         0 $isneg = 0;
1071 0 0 0     0 if ($v =~ /^\D*\-/o) {
    0 0        
    0          
1072 0         0 $isneg = 1;
1073             } elsif ($special =~ /\(/o && $v =~ /\(/o) {
1074 0         0 $isneg = 1;
1075             } elsif ($suffix =~ /^[\_ ]*CR\s*$/o && $v =~ s/\s*CR\s*$//o) {
1076 0 0       0 unless ($ops->{-nonnumeric}) {
1077 0         0 $isneg = 1;
1078             }
1079             }
1080 0 0       0 $v =~ s/[\Q$special\E]//g if ($special);
1081 0         0 $v =~ s/^\s+//o;
1082 0         0 $v =~ s/\s+$//o;
1083 0 0       0 $v =~ s/\Q${suffix}\E$// unless ($ops->{'-suffix'} =~ /no/io);
1084 0         0 $v =~ s/\s+$//o;
1085 0 0       0 if ($isneg) {
1086 0 0       0 $v = '-' . $v unless ($v =~ /^\-/o);
1087             }
1088 0         0 $pic =~ s/(\d+)[<|>]?([\.\^]?)(\d*)([<|>])/
1089 0         0 my ($one, $dec, $two, $three) = ($1, $2, $3, $4);
1090 0   0     0 $dec ||= '.';
1091 0         0 my $exp = ($three x $one);
1092 0 0       0 $exp .= $dec . ($three x $two) if ($two > 0);
1093 0         0 $exp
1094             /e; #CONVERT STUFF LIKE "@12.2>" TO "@12>.2>".
1095 0 0       0 my $justify = ($pic =~ /^.*?([<|>])/o) ? $1 : '';
1096 0         0 my $decJustify;
1097 0 0       0 if ($pic =~ /^([<|>]+)[\.\^]([<|>]+)/o) {
    0          
1098 0         0 my $two = $2;
1099 0         0 $leni = length($1) + length($two) + 2;
1100 0 0       0 unless ($ops->{'-nonnumeric'}) {
1101 0         0 my $decLen = length($two);
1102 0 0       0 $decJustify = ($two =~ /([\<\|\>])$/o) ? $1 : '';
1103 0 0 0     0 if ($pic !~ /\./o && $v =~ /\./ && $v =~ /^[\+\-\d\. ]+$/o) { #WE HAVE AN "IMPLIED DECIMAL POINT!
      0        
1104 0 0       0 $v = sprintf("%.0f", $v * (10**$decLen)) if ($v =~ /^[\+\-\d\. ]+$/o);
1105             } else {
1106 0         0 $v = sprintf("%.${decLen}f", $v);
1107             }
1108             }
1109             } elsif ($pic =~ /^([\[\<\|\>]+)/o) {
1110 0         0 $leni = length($1) + 1;
1111             } else {
1112 0         0 $leni = 1;
1113             }
1114 0 0 0     0 $leni = $leniSpecified if ($leniSpecified && $leni > $leniSpecified);
1115 0 0 0     0 if ($leni && length($v) > $leni) {
1116 0 0 0     0 if ($decJustify eq '>' && !$ops->{'-nonnumeric'} && $v =~ /^[0-9\+\-]*\.[0-9]+/o) { #(NUMERIC) CHOP OFF DECIMALS UNTIL IT EITHER FITS OR WE ARE A WHOLE NUMBER:
      0        
1117 0         0 while (length($v) > $leni) {
1118 0         0 chop($v);
1119 0 0       0 last unless ($v =~ /\./o);
1120             }
1121 0 0       0 $v = '0' unless (length($v));
1122             }
1123             }
1124 0 0 0     0 if ($leni && length($v) > $leni) {
1125 0 0       0 if ($ops->{'-truncate'} !~ /no/io) {
1126 0 0       0 if ($ops->{'-truncate'} =~ /er/io) {
1127 0         0 $v = $errchar x $leni;
1128             } else {
1129 0 0       0 if ($justify eq '>') { #CHOP LEADING CHARACTERS UNTIL FITS IF RIGHT-JUSTIFY:
1130 0         0 while (length($v) > $leni) {
1131 0         0 $v =~ s/^.(.+)$/$1/;
1132             }
1133             } else { #CHOP TRAILING CHARACTERS UNTIL FITS IF LEFT-JUSTIFY|CENTER:
1134 0         0 while (length($v) > $leni) {
1135 0         0 chop $v;
1136             }
1137             }
1138             }
1139             }
1140             }
1141 0         0 my $padcnt = $leniSpecified - length($v);
1142 0 0       0 if ($padcnt > 0) {
1143 0 0       0 if ($justify eq '>') {
    0          
1144 0         0 $v = (' ' x $padcnt) . $v;
1145             } elsif ($justify eq '|') {
1146 0         0 for (my $i=0;$i<$padcnt;$i++) {
1147 0 0       0 $v = ($i % 2) ? ' ' . $v : $v . ' ';
1148             }
1149             } else {
1150 0         0 $v .= ' ' x $padcnt;
1151             }
1152             }
1153 0 0       0 return wantarray ? ($v, length($v), $justify) : $v;
1154             }
1155             } elsif ($pic =~ s/^\=//o) { #FIELDS STARTING WITH "=" ARE TO BE WRAPPED TO MULTIPLE LINES AS NEEDED:
1156 0         0 my ($justify, $wrapchar) = ('<', 'W'); #DEFAULTS.
1157 0         0 my $j = 1;
1158 0 0       0 $suffix = ($pic =~ s/([^wW<|>\d]+)$//o) ? $1 : '';
1159 0 0       0 $wrapchar = 'w' if ($pic =~ /w/o); #LITTLE w=WRAP AT CHARACTER:
1160 0 0       0 $justify = $1 if ($pic =~ /^.*([<|>])/o); #BIG W=WRAP AT WORD BOUNDARIES (Text::Wrap):
1161 0         0 $v =~ s/${suffix}(\r?\n)/$1/gs;
1162 0 0       0 if ($justify eq '<') {
    0          
1163 0         0 $v =~ s/(\S)\r?\n\s*/$1 /gs;
1164             } elsif ($justify eq '>') {
1165 0         0 $v =~ s/\s*\r?\n(\S)/ $1/gs;
1166             } else {
1167 0         0 $v =~ s/\s*\r?\n\s*/ /gs;
1168             }
1169 0         0 $v =~ s/\r?\n//gs;
1170 0 0       0 $leni = $leniSpecified if ($leni > $leniSpecified);
1171 0 0 0     0 if ($leni && length($v) > $leni) {
    0 0        
1172 0 0       0 if ($ops->{'-truncate'} !~ /no/io) {
1173 0 0       0 $v = ($ops->{'-truncate'} =~ /er/io) ? $errchar x $leni : substr($v, 0, $leni);
1174             }
1175             } elsif ($leniSpecified && length($v) < $leniSpecified) {
1176 0         0 my $padcnt = $leniSpecified - length($v);
1177 0 0       0 if ($padcnt > 0) {
1178 0 0       0 $v = ($justify eq '>') ? (' ' x $padcnt) . $v : $v . (' ' x $padcnt);
1179             }
1180             }
1181 0 0       0 return wantarray ? ($v, length($v), $justify) : $v;
1182             } elsif ($pic =~ s/^\%//o) { #C-PRINTF FORMAT STRINGS (%-STRINGS) (AS-IS, "%" NOT INCLUDED IN FIELD SIZE):
1183 0 0       0 my $float = ($pic =~ s/^\$//o) ? '$' : ''; #EXCEPTION: FLOATING $, COMMA(COMMATIZE) ALLOWED AFTER "%":
1184 0 0       0 my $commatize = ($pic =~ s/^\,//o) ? 1 : 0; #IE: "%$,-14.2f": FIELD SIZE=16!
1185 0 0       0 $v =~ s/$2$// if ($pic =~ s/^(\-?[\d\.]+\w)(.*)$/$1/o);
1186 0 0       0 $leni = ($pic =~ /^\-?(\d+)/) ? $1 : length($v);
1187 0 0       0 my $lj = ($pic =~ /^\-/o) ? '-' : '';
1188 0 0       0 $justify = ($lj eq '-') ? '<' : '>';
1189 0         0 $pic = '%' . $pic;
1190 0         0 my $t;
1191 0 0       0 my $decimal = ($pic =~ /\.(\d+)/o) ? $1 : 0;
1192 0 0       0 if ($float) {
1193 0         0 $lj = '';
1194 0 0       0 $lj = '-' if ($pic =~ s/^\%\-/\%/o);
1195 0 0       0 $leni += length($float) if ($pic =~ /^\%(\d+)/o);
1196 0         0 $v = sprintf("%.${decimal}f", $v);
1197             }
1198 0         0 my $l;
1199 0 0       0 if ($commatize) {
1200 0 0       0 $leni++ if ($pic =~ /^\%(\d+)/o);
1201 0         0 $l = length($v);
1202 0   0     0 while ($l > $leni && $v =~ /\./o) {
1203 0         0 chop $v;
1204             }
1205 0 0       0 if ($l > $leni) {
1206 0         0 $v = '#'x$leni;
1207 0 0       0 return wantarray ? ($v, $leni, $justify) : $v;
1208             }
1209 0   0     0 while ($l < $leni && $v =~ s/(\d)(\d\d\d)\b/$1,$2/) {
1210 0         0 $l = length($v);
1211             }
1212             } else {
1213 0         0 $l = length($v);
1214 0   0     0 while ($l > $leni && $v =~ /\./o) { #CHOP OFF DECIMAL PLACES IF NEEDED TO GET TO FIT:
1215 0         0 chop $v;
1216             }
1217             }
1218 0 0 0     0 $v = $float . $v if ($float && $l < $leni);
1219 0         0 $v = sprintf("%${lj}${leni}.${leni}s", $v);
1220 0 0       0 return wantarray ? ($v, $leni, $justify) : $v;
1221             } else {
1222 0         0 return undef; #INVALID PICTURE STRING:
1223             }
1224             }
1225              
1226             sub fmtsiz {
1227 1     1 1 447 my $pic = shift;
1228 1         2 my $v = shift;
1229 1         8 my $leni;
1230             my $suffix;
1231 1 50       6 if ($pic =~ s/^\@//o) { #@-strings:
    0          
    0          
1232 1 50       17 if ($pic =~ /^(\d+)\:/o) {
    50          
    50          
    50          
    50          
1233 0         0 return $1;
1234             } elsif ($pic =~ s/^([\'\"\/\`])//o) { #PICTURE LITERAL (@'foo'
1235 0         0 my $regexDelimiter = $1; #REPLACE EACH DOT WITH NEXT CHAR. SKIP ONES CORRESPONDING WITH "^", ALL OTHER CHARS ARE LITERAL.
1236 0         0 $pic =~ s#\Q$regexDelimiter\E.*$##;
1237 0         0 my $cnt = 0; #EXAMPLE: fmt("@\"...-..-.+\";suffix", '123456789'); FORMATS AN SSN:
1238 0         0 my $frompic = '';
1239 0         0 $pic =~ s/\\\+/\x02/go;
1240 0         0 $pic =~ s/\\\./\x03/go;
1241 0         0 $pic =~ s/\\\^/\x04/go;
1242 0         0 return length($pic);
1243             } elsif ($pic =~ s#^\^##o) { #DATE-CONVERSION
1244 0         0 $pic =~ s#\^.*$##;
1245 0         0 $pic =~ s/\x03/\)/go;
1246 0         0 $pic =~ s/\x02/\(/go;
1247 0         0 (my $t = $v) =~ s/\D//go;
1248 0         0 return length($pic);
1249             } elsif ($pic =~ m#^(?:s|tr)\W#o) { #REGEX SUBSTITUTION (@s/foo/bar/)
1250 0         0 return 0;
1251             } elsif ($pic =~ /^[a-zA-Z_]+/o) { #USER-SUPPLIED FUNCTION (@foo('*'))
1252 0         0 return 0;
1253             } else { #REGULAR STUFF, IE. @12>.>>)
1254 1 50       4 return 0 if ($pic =~ /^\*(.*)$/o);
1255 1         4 $pic =~ s/[^\<\|\>\.\^]+$//o;
1256 1         30 my ($special, $float, $t);
1257 1         3 my $commatize = 0;
1258 1         6 while ($pic =~ s/^([^\d\<\|\>\.\^])//o) { #STRIP OFF ALL CHARS BEFORE <, >, |, OR DIGIT AS "FLOATING CHARS".
1259 2         5 $special = $1;
1260 2 100       6 if ($special eq ',') { #COMMA (@,) = ADD COMMAS EVERY 3 DIGITS:
1261 1         4 $commatize = 1;
1262             } else {
1263 1         5 $float .= $special; #OTHERS, IE. (@$) ARE FLOATERS:
1264             }
1265             }
1266 1         3 $leni = 1 + length($float) + $commatize; #COUNT FLOATING CHARS IN FIELD SIZE:
1267 1         6 $pic =~ s/(\d+)[<|>]?([\.\^]?)(\d*)([<|>])/
1268 1         4 my ($one, $dec, $two, $three) = ($1, $2, $3, $4);
1269 1   50     4 $dec ||= '.';
1270 1         3 my $exp = ($three x $one);
1271 1 50       5 $exp .= $dec . ($three x $two) if ($two > 0);
1272 1         3 $exp
1273             /e; #CONVERT STUFF LIKE "@12.2>" TO "@12>.2>".
1274 1         2 $t = $pic;
1275             #CALCULATE FIELD SIZE BASED ON NO. OF "<, >, |" AND PRECEEDING REPEATER DIGITS:
1276 1         8 $leni += length($1) while ($t =~ s/([\<\|\>\.\^\,\$]+)//o);
1277 1         9 $leni += $1 - 1 while ($t =~ s/(\d+)//o);
1278 1         3 return $leni;
1279             }
1280             } elsif ($pic =~ s/^\=//o) { #FIELDS STARTING WITH "=" ARE TO BE WRAPPED TO MULTIPLE LINES AS NEEDED:
1281 0         0 my ($justify, $wrapchar) = ('<', 'W'); #DEFAULTS.
1282 0         0 my $j = 1;
1283 0 0       0 $suffix = $1 if ($pic =~ s/([^wW<|>\d]+)$//o);
1284 0 0       0 $wrapchar = 'w' if ($pic =~ /w/o); #LITTLE w=WRAP AT CHARACTER:
1285 0 0       0 $justify = $1 if ($pic =~ /^.*([<|>])/o); #BIG W=WRAP AT WORD BOUNDARIES (Text::Wrap):
1286 0         0 $j += length($1) while ($pic =~ s/([wW<|>]+)//o);
1287 0         0 $j += $1 - 1 while ($pic =~ s/(\d+)//o);
1288 0         0 return $j; #WIDTH OF FIELD AREA TO WRAP WITHIN:
1289             } elsif ($pic =~ s/^\%//o) { #C-PRINTF FORMAT STRINGS (%-STRINGS) (AS-IS, "%" NOT INCLUDED IN FIELD SIZE):
1290 0 0       0 my $float = ($pic =~ s/^\$//o) ? '$' : ''; #EXCEPTION: FLOATING $, COMMA(COMMATIZE) ALLOWED AFTER "%":
1291 0 0       0 my $commatize = ($pic =~ s/^\,//o) ? 1 : 0; #IE: "%$,-14.2f": FIELD SIZE=16!
1292 0 0       0 $suffix = ($pic =~ s/^(\-?[\d\.]+\w)(.*$)/$1/o) ? $2 : '';
1293 0 0       0 $leni = ($pic =~ /^\-?(\d+)/) ? $1 : length($v);
1294 0 0       0 my $lj = ($pic =~ /^\-/o) ? '-' : '';
1295 0         0 $pic = '%' . $pic;
1296 0         0 my $t;
1297 0 0       0 if ($float) {
1298 0         0 $pic =~ s/^\%\-/\%/o;
1299 0 0       0 $leni += length($float) if ($pic =~ /^\%(\d+)/o);
1300             }
1301 0 0       0 if ($commatize) {
1302 0 0       0 $leni++ if ($pic =~ /^\%(\d+)/o);
1303             }
1304 0         0 return $leni;
1305             } else {
1306 0         0 return undef; #INVALID PICTURE STRING:
1307             }
1308             }
1309              
1310             sub fmtjust {
1311 0     0 1 0 my $pic = shift;
1312 0         0 my $v = shift;
1313              
1314 0         0 my $leni;
1315             my $suffix;
1316 0 0       0 if ($pic =~ s/^\@//o) { #@-strings:
    0          
    0          
1317 0         0 $pic =~ s/(\d+)\://o;
1318 0 0       0 if ($pic =~ s/^[\'\"\/\`]//o) { #PICTURE LITERAL (@'foo'
    0          
    0          
    0          
1319 0         0 return '<';
1320             } elsif ($pic =~ s#^\^##o) { #DATE-CONVERSION
1321 0         0 return '<';
1322             } elsif ($pic =~ m#^(?:s|tr)\W#o) { #REGEX SUBSTITUTION (@s/foo/bar/)
1323 0         0 return '<';
1324             } elsif ($pic =~ /^[a-zA-Z_]+/o) { #USER-SUPPLIED FUNCTION (@foo('*'))
1325 0         0 return '<';
1326             } else { #REGULAR STUFF, IE. @12>.>>)
1327 0 0       0 return '<' if ($pic =~ /^\*(.*)$/);
1328 0 0       0 $suffix = $1 if ($pic =~ s/([^\<\|\>\.\^]+)$//o);
1329 0         0 my ($special, $float, $t);
1330 0         0 my $commatize = 0;
1331 0         0 while ($pic =~ s/^([^\d\<\|\>\.\^])//o) { #STRIP OFF ALL CHARS BEFORE <, >, |, OR DIGIT AS "FLOATING CHARS".
1332 0         0 $special = $1;
1333 0 0       0 if ($special eq ',') { #COMMA (@,) = ADD COMMAS EVERY 3 DIGITS:
1334 0         0 $commatize = 1;
1335             } else {
1336 0         0 $float .= $special; #OTHERS, IE. (@$) ARE FLOATERS:
1337             }
1338             }
1339 0 0       0 if ($float =~ /\(/o) #ONLY KEEP FLOATING "(" IF SUFFIX STARTS WITH A ")"!
1340             {
1341 0 0       0 $float =~ s/\(//o unless ($suffix =~ s/^\)//o);
1342             }
1343 0 0       0 if ($v < 0)
1344             {
1345 0         0 $float =~ s/\+//go; #REMOVE FLOATING "+" IF VALUE IS NEGATIVE.
1346             }
1347             else
1348             {
1349 0         0 $float =~ s/\(//go; #REMOVE FLOATING "(..)" IF VALUE IS NOT NEGATIVE.
1350             }
1351 0         0 $leni = 1 + length($float) + $commatize; #COUNT FLOATING CHARS IN FIELD SIZE:
1352 0 0       0 my $justify = ($pic =~ /^.*?([<|>])/o) ? $1 : '';
1353             #DEFAULT JUSTIFY: RIGHT IF COMMATIZING(NUMBER) OR FLOATING$ OR PICTURE CONTAINS DECIMAL;
1354             #OTHERWISE, DEFAULT IS LEFT.
1355 0 0 0     0 $justify ||= ($commatize || $float =~ /\$/o || $pic =~ /[.,\$]/o) ? '>' : '<';
      0        
1356 0         0 return $justify;
1357             }
1358             } elsif ($pic =~ s/^\=//o) { #FIELDS STARTING WITH "=" ARE TO BE WRAPPED TO MULTIPLE LINES AS NEEDED:
1359 0         0 my ($justify, $wrapchar) = ('<', 'W'); #DEFAULTS.
1360 0         0 my $j = 1;
1361 0 0       0 $suffix = $1 if ($pic =~ s/([^wW<|>\d]+)$//o);
1362 0 0       0 $wrapchar = 'w' if ($pic =~ /w/o); #LITTLE w=WRAP AT CHARACTER:
1363 0 0       0 $justify = $1 if ($pic =~ /^.*([<|>])/o); #BIG W=WRAP AT WORD BOUNDARIES (Text::Wrap):
1364 0         0 return $justify;
1365             } elsif ($pic =~ s/^\%//o) { #C-PRINTF FORMAT STRINGS (%-STRINGS) (AS-IS, "%" NOT INCLUDED IN FIELD SIZE):
1366 0 0       0 my $float = ($pic =~ s/^\$//o) ? '$' : ''; #EXCEPTION: FLOATING $, COMMA(COMMATIZE) ALLOWED AFTER "%":
1367 0 0       0 my $commatize = ($pic =~ s/^\,//o) ? 1 : 0; #IE: "%$,-14.2f": FIELD SIZE=16!
1368 0 0       0 $suffix = ($pic =~ s/^(\-?[\d\.]+\w)(.*$)/$1/o) ? $2 : '';
1369 0 0       0 $leni = ($pic =~ /^\-?(\d+)/) ? $1 : length($v);
1370 0 0       0 my $justify = ($pic =~ /^\-/o) ? '<' : '>';
1371 0         0 return $justify;
1372             } else {
1373 0         0 return undef; #INVALID PICTURE STRING:
1374             }
1375 0         0 return '<';
1376             }
1377              
1378             sub fmtsuffix {
1379 0     0 1 0 my $pic = shift;
1380 0         0 my $v = shift;
1381 0         0 my $ops = shift;
1382              
1383 0         0 my $leni;
1384 0         0 my $suffix = '';
1385 0 0       0 if ($pic =~ s/^\@//o) { #@-strings:
    0          
    0          
1386 0         0 $pic =~ s/(\d+)\://o;
1387 0 0       0 if ($pic =~ s/^([\'\"\/\`])//o) { #PICTURE LITERAL (@'foo'
    0          
    0          
    0          
1388 0         0 my $regexDelimiter = $1; #REPLACE EACH DOT WITH NEXT CHAR. SKIP ONES CORRESPONDING WITH "^", ALL OTHER CHARS ARE LITERAL.
1389 0 0       0 $suffix = $1 if ($pic =~ s#\Q$regexDelimiter\E(.*)$##);
1390 0         0 return $suffix;
1391             } elsif ($pic =~ s#^\^##o) { #DATE-CONVERSION
1392 0 0       0 $suffix = $1 if ($pic =~ s#\^(.*)$##o);
1393 0         0 return $suffix;
1394             } elsif ($pic =~ m#^(?:s|tr)(\W)#) { #REGEX SUBSTITUTION (@s/foo/bar/)
1395 0         0 my $regexDelimiter = $1;
1396 0 0       0 $suffix = $1 if ($pic =~ s#([^$regexDelimiter]+)$##);
1397 0         0 return $suffix;
1398             } elsif ($pic =~ /^[a-zA-Z_]+/o) { #USER-SUPPLIED FUNCTION (@foo('*'))
1399 0 0       0 $suffix = $1 if ($pic =~ s/\)([^\)]*)$/\)/o);
1400 0         0 return $suffix;
1401             } else { #REGULAR STUFF, IE. @12>.>>)
1402 0 0       0 return $1 if ($pic =~ /^\*(.*)$/);
1403 0 0       0 $suffix = $1 if ($pic =~ s/([^<|>.]+)$//o);
1404 0         0 return $suffix;
1405             }
1406             } elsif ($pic =~ s/^\=//o) { #FIELDS STARTING WITH "=" ARE TO BE WRAPPED TO MULTIPLE LINES AS NEEDED:
1407 0         0 my ($justify, $wrapchar) = ('<', 'W'); #DEFAULTS.
1408 0         0 my $j = 1;
1409 0 0       0 $suffix = $1 if ($pic =~ s/([^wW<|>\d]+)$//o);
1410 0         0 return $suffix;
1411             } elsif ($pic =~ s/^\%//o) { #C-PRINTF FORMAT STRINGS (%-STRINGS) (AS-IS, "%" NOT INCLUDED IN FIELD SIZE):
1412 0         0 $pic =~ s/^\$//o;
1413 0         0 $pic =~ s/^\,//o;
1414 0 0       0 $suffix = ($pic =~ s/^(\-?[\d\.]+\w)(.*)$/$1/o) ? $2 : '';
1415 0         0 return $suffix;
1416             } else {
1417 0         0 return undef; #INVALID PICTURE STRING:
1418             }
1419             }
1420              
1421             sub _chkdate# #CONVER USER-ENTERED DATES TO "yyyymmdd".
1422             {
1423             #### Y2K COMPLIANT UNTIL 2080.
1424             #### NOTE: 6-DIGIT DATES W/SEPARATORS ARE HANDLED AS mmddyy!
1425             #### NOTE: 6-DIGIT INTEGER DATES ARE HANDLED AS yymmdd!
1426            
1427 1     1   3 my ($dt) = shift;
1428 1         3 my ($res);
1429 1 0       5 return wantarray ? ($dt,0) : $dt unless ($dt =~ /\S/o);
    50          
1430 1 50       8 $dt = substr($dt,0,8) . ' ' . substr($dt,8) if ($dt =~ /\d{9,14}\D*$/o);
1431 1 50 0     6 if ($dt =~ s#(\d+)[\/\-\.](\d+)[\/\-\.](\d+)##o)
    0          
1432             {
1433 1         2 my $x;
1434 1 50 33     16 if ($1 < 1000 && $3 < 1000) #user entered: "mm/dd/yy"|"mm-dd-yy"|"mm.dd.yy"
    50          
1435             {
1436 0 0       0 my $century = ($3 < 80) ? 20 : 19; #Y2K:80-99=19##; 00-79=20##!
1437 0         0 $x = sprintf '%-2.2d%-2.2d%-2.2d%-2.2d',$century,$3,$1,$2
1438             }
1439             elsif ($1 > 1000) #user entered: "yyyy/mm/dd"|"yyyy-mm-dd"|"yyyy.mm.dd"
1440             {
1441 1         8 $x = sprintf '%-2.2d%-2.2d%-2.2d',$1,$2,$3;
1442             }
1443             else #user entered: "mm/dd/yyyy"|"mm-dd-yyyy"|"mm.dd.yyyy"
1444             {
1445 0         0 $x = sprintf '%-2.2d%-2.2d%-2.2d',$3,$1,$2;
1446             }
1447 1         3 my $then = 0;
1448 1 50       5 if ($dt =~ s#^\D+(\d\d?)\:?(\d\d?)##o)
1449             {
1450 1         5 $x .= ' ' . sprintf '%-2.2d%-2.2d',$1,$2;
1451 1 50       7 $x .= ($dt =~ s#\:?(\d\d?)##o) ? sprintf('%-2.2d',$1) : '00';
1452 1 50       7 if ($dt =~ m#(\s*[ap]m?)#i)
1453             {
1454 1         2 my $indicator = $1;
1455 1 50       7 my $hr = $1 if ($x =~ /\d (\d\d)/);
1456 1 50 33     18 if ($indicator =~ /a/i && $hr == 12)
    50 33        
1457             {
1458 0         0 $x =~ s/(\d) (\d\d)/$1 . ' 00'/e;
  0         0  
1459             }
1460             elsif ($indicator =~ /p/i && $hr != 12)
1461             {
1462 1         5 $x =~ s/(\d) (\d\d)/$1 . ' ' . sprintf('%-2.2d',$hr+12)/e;
  1         6  
1463             }
1464 1         2 $x .= $indicator;
1465             }
1466             eval
1467 1         3 {
1468 1         10 $then = &timelocal(substr($x,13,2),substr($x,11,2),substr($x,9,2),
1469             substr($x,6,2),(substr($x,4,2)-1),substr($x,0,4),0,0,0);
1470             };
1471             }
1472             else
1473             {
1474             eval
1475 0         0 {
1476 0         0 $then = &timelocal(0,0,0,substr($x,6,2),
1477             (substr($x,4,2)-1),substr($x,0,4),0,0,0);
1478             };
1479             }
1480 1         94 $dt = $x;
1481              
1482              
1483 1 50       4 $dt = '' unless ($then > 0); #INVALID DATE, BLANK OUT!
1484 1 50       7 return wantarray ? ($dt, $then) : $dt;
1485             }
1486             elsif ($dt =~ s/^(\d\d\d\d\d\d+)(\D+\d+\:?\d+.*)?$/$1/o || $dt =~ s/^(\d{8})(\d{4})/$1/o)
1487             {
1488 0   0     0 my $timepart = $2 || '';
1489 0 0       0 if (length($dt) == 6) #user entered: "yymmdd"
1490             {
1491 0 0       0 my $century = (substr($dt,0,2) < 80) ? 20 : 19; #Y2K:80-99=19##; 00-79=20##!
1492 0         0 $dt = $century . $dt;
1493             }
1494             else #user entered: "mmddyyyy"
1495             {
1496 0         0 my ($leftpart) = substr($dt,0,4);
1497 0 0       0 if ($leftpart < 1300) #user entered: "mmddyyyy"
1498             {
1499 0         0 $dt = substr($dt,4,4) . $leftpart;
1500             }
1501             }
1502 0         0 my $then = 0;
1503 0         0 $timepart =~ s/^\D+//o;
1504 0 0 0     0 if ($timepart =~ s#^(\d\d)(\d\d)##o || $timepart =~ s#^(\d\d?)\:(\d\d?)\:?##o)
1505             {
1506 0         0 $dt .= ' ' . sprintf('%-2.2d',$1) . sprintf('%-2.2d',$2);
1507 0 0       0 $dt .= ($timepart =~ s#(\d\d?)\s*##o) ? sprintf('%-2.2d',$1) : '00';
1508 0 0       0 if ($timepart =~ m#([ap]m?)#io)
1509             {
1510 0         0 my $indicator = $1;
1511 0 0       0 my $hr = $1 if ($dt =~ /\d (\d\d)/);
1512 0 0 0     0 if ($indicator =~ /a/i && $hr == 12)
    0 0        
1513             {
1514 0         0 $dt =~ s/(\d) (\d\d)/$1 . ' 00'/e;
  0         0  
1515             }
1516             elsif ($indicator =~ /p/i && $hr != 12)
1517             {
1518 0         0 $dt =~ s/(\d) (\d\d)/$1 . sprintf('%-2.2d',$hr+12)/e;
  0         0  
1519             }
1520             }
1521             eval
1522 0         0 {
1523 0         0 $then = &timelocal(substr($dt,13,2),substr($dt,11,2),substr($dt,9,2),
1524             substr($dt,6,2),(substr($dt,4,2)-1),substr($dt,0,4),0,0,0);
1525             };
1526             }
1527             else
1528             {
1529 0         0 eval {
1530 0         0 $then = &timelocal(0,0,0,substr($dt,6,2),
1531             (substr($dt,4,2)-1),substr($dt,0,4),0,0,0);
1532             };
1533             }
1534 0 0       0 $dt = '' unless ($then > 0); #INVALID DATE, BLANK OUT!
1535 0 0       0 return wantarray ? ($dt, $then) : $dt;
1536             }
1537             else
1538             {
1539 0 0       0 return wantarray ? ('', 0) : ''; #INVALID DATE, BLANK OUT!
1540             }
1541             }
1542              
1543             1
1544              
1545             __END__