1 package BSE::Util::Tags;
5 use BSE::Util::HTML qw(:default escape_xml);
6 use vars qw(@EXPORT_OK @ISA);
7 @EXPORT_OK = qw(tag_error_img tag_hash tag_hash_plain tag_hash_mbcs tag_article tag_article_plain tag_object);
12 my ($acts, $args) = @_;
15 while (length $args) {
16 if ($args =~ s/^\s*\[\s*(\w+)(?:\s+(\S[^\]]*))?\]\s*//) {
17 my ($func, $subargs) = ($1, $2);
19 $subargs = '' unless defined $subargs;
20 push(@out, $acts->{$func}->($subargs));
23 elsif ($args =~ s/^\s*\"((?:[^\"\\]|\\[\\\"])*)\"\s*//) {
25 $out =~ s/\\([\\\"])/$1/g;
28 elsif ($args =~ s/^\s*(\S+)\s*//) {
40 my ($cfg, $fmt, $sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = @_;
47 my @when = ( $sec, $min, $hour, $day, $month, $year, $wday, $wday, $isdst );
50 @when = localtime POSIX::mktime(@when);
53 $fmt =~ s/(?<!%)((?:%%)*)%F/$1%Y-%m-%d/g;
54 return Date::Format::strftime($fmt, \@when);
59 return POSIX::strftime($fmt, $sec, $min, $hour, $day, $month, $year, $wday, $wday, $isdst);
64 my ($cfg, $args, $acts, $tag_name, $templater) = @_;
67 if ($args =~ s/((?:sort|filter)=.*)//s) {
71 my ($section) = DevHelp::Tags->get_parms($args, $acts, $templater)
74 my %entries = $cfg->entries($section);
75 my @entries = map +{ key => $_, value => $entries{$_} }, keys %entries;
80 unless (grep /\D/, keys %entries) {
83 unless (grep /\D/, values %entries) {
89 return BSE::Sort::bse_sort(\%types, $sort_filter, @entries);
96 return escape_html(BSE::CfgInfo::admin_base_url($cfg));
100 my ($class, $acts, $cfg) = @_;
102 my $static_ajax = $cfg->entry('basic', 'staticajax', 0);
103 require BSE::Util::Iterate;
104 my $it = BSE::Util::Iterate->new;
109 my ($arg, $acts, $name, $templater) = @_;
110 my ($quote, $fmt, $func, $args) =
111 $arg =~ m/(?:([\"\'])([^\"\']+)\1\s+)?(\S+)(?:\s+(\S+.*))?/;
112 $fmt = "%d-%b-%Y" unless defined $fmt;
113 exists $acts->{$func}
114 or return "<:date $_[0]:>";
115 my $date = $templater->perform($acts, $func, $args)
117 my ($year, $month, $day, $hour, $min, $sec) =
118 $date =~ /(\d+)\D+(\d+)\D+(\d+)(?:\D+(\d+)\D+(\d+)\D+(\d+))?/;
119 $hour = $min = $sec = 0 unless defined $sec;
122 # passing the isdst as 0 seems to provide a more accurate result than
124 return bse_strftime($cfg, $fmt, $sec, $min, $hour, $day, $month, $year, -1, -1, -1);
126 today => [ \&tag_today, $cfg ],
129 my ($arg, $acts, $name, $templater) = @_;
130 my ($func, $args) = split ' ', $arg, 2;
131 $args = '' unless defined $args;
132 exists $acts->{$func}
133 or return "<: money $func $args :>";
134 my $value = $templater->perform($acts, $func, $args);
138 # or print STDERR "Result '$value' from [$func $args] not a number\n";
139 $value =~ /\d/ or $value = 0;
140 sprintf("%.02f", $value/100.0);
142 number => \&tag_number,
145 my ($arg, $acts, $name, $templater) = @_;
146 my ($func, $args) = split ' ', $arg, 2;
148 $args = '' unless defined $args;
149 exists $acts->{$func}
150 or return "<: bodytext $func $args :>";
151 my $value = $templater->perform($acts, $func, $args);
155 $value = decode_entities($value);
157 my $gen = Generate->new(cfg=>$cfg);
158 return $gen->format_body(acts => $acts,
159 articles => 'Articles',
161 templater => $templater);
163 nobodytext => [\&tag_nobodytext, $cfg ],
166 my ($arg, $acts, $name, $templater) = @_;
167 my @args = DevHelp::Tags->get_parms($arg, $acts, $templater);
169 or die "wrong number of args (@args)";
170 #print STDERR "ifEq >$left< >$right<\n";
171 $args[0] eq $args[1];
175 my ($arg, $acts, $name, $templater) = @_;
176 (my ($left, $right) = DevHelp::Tags->get_parms($arg, $acts, $templater)) == 2
177 or die; # leaves if in place
182 my @args = DevHelp::Tags->get_parms(@_[0,1,3]);
183 for my $item (@args) {
190 my @args = DevHelp::Tags->get_parms(@_[0,1,3]);
191 for my $item (@args) {
192 return 0 unless $item;
198 my ($args, $acts, $myfunc, $templater) = @_;
199 my ($section, $key, $def) =
200 DevHelp::Tags->get_parms($args, $acts, $templater);
202 defined $def or $def = '';
203 $cfg->entry($section, $key, $def);
205 $it->make_iterator([ \&iter_cfgsection, $cfg ], 'cfgentry', 'cfgsection'),
208 my ($arg, $acts, $name, $templater) = @_;
209 my ($key, $args) = split ' ', $arg, 2;
210 $acts->{$key} or return "<:kb $arg:>";
211 my $value = $templater->perform($acts, $key, $args);
212 if ($value > 100000) {
213 return sprintf("%.0fk", $value/1000.0);
215 elsif ($value > 1000) {
216 return sprintf("%.1fk", $value/1000.0);
224 require BSE::Version;
225 BSE::Version->version;
229 my ($arg, $acts, $name, $templater) = @_;
230 my @items = DevHelp::Tags->get_parms($arg, $acts, $templater);
232 $sum += $_ for @items;
237 my ($arg, $acts, $name, $templater) = @_;
238 my @items = DevHelp::Tags->get_parms($arg, $acts, $templater);
241 arithmetic => \&tag_arithmetic,
244 my ($arg, $acts, $name, $templater) = @_;
245 my ($str, $re, $out, $def)
246 = DevHelp::Tags->get_parms($arg, $acts, $templater);
247 $re or return '** no regexp supplied to match **';
249 defined $def or $def = '';
250 my @matches = $str =~ /$re/
252 defined or $_ = '' for @matches;
254 $out =~ s/\$([1-9\$])/
255 $1 eq '$' ? '$' : $1 <= @matches ? $matches[$1-1] : '' /ge;
259 replace => \&tag_replace,
262 my ($arg, $acts, $name, $templater) = @_;
263 my @items = DevHelp::Tags->get_parms($arg, $acts, $templater);
268 my ($arg, $acts, $name, $templater) = @_;
269 my @items = DevHelp::Tags->get_parms($arg, $acts, $templater);
274 my ($arg, $acts, $name, $templater) = @_;
275 my @items = DevHelp::Tags->get_parms($arg, $acts, $templater);
276 lcfirst join '', @items;
280 my ($arg, $acts, $name, $templater) = @_;
281 my @items = DevHelp::Tags->get_parms($arg, $acts, $templater);
282 ucfirst join '', @items;
286 my ($arg, $acts, $name, $templater) = @_;
287 my @items = DevHelp::Tags->get_parms($arg, $acts, $templater);
288 my $out = join '', @items;
289 $out = lc $out; # start all lowercase
290 $out =~ s/(^'?|\W'|[^'\w])(\w)/$1\U$2/g;
293 adminbase => [ \&tag_adminbase, $cfg ],
294 help => [ \&tag_help, $cfg, 'user' ],
295 $it->make_iterator(\&DevHelp::Tags::iter_get_repeat, 'strepeat', 'strepeats'),
296 report => [ \&tag_report, $cfg ],
298 # the following is so you can embed a report in another report, since
299 # report conflicts with a tag name used within reports
300 subreport => [ \&tag_report, $cfg ],
305 ajax => [ \&tag_ajax, $cfg ],
316 my ($value, $fmt) = @_;
318 return escape_uri($value);
320 elsif ($fmt eq 'U') {
321 return escape_uri(unescape_html($value));
323 elsif ($fmt eq 'h') {
324 return escape_html($value);
326 elsif ($fmt eq 'x') {
327 return escape_xml(unescape_html($value));
329 elsif ($fmt eq 'z') {
330 return unescape_html($value);
332 elsif ($fmt eq 'c') {
333 my $workset = $cfg->entry('html', 'charset', 'iso-8859-1');
335 my $work = unescape_html($value);
336 Encode::from_to($work, 'utf8', $workset);
345 my ($arg, $acts, $name, $templater) = @_;
349 if ($arg =~ s/^\s*([^:\s]*)://) {
357 $arg =~ s/(\[\s*([^\W\d]\w*)(\s+\S[^\[\]]*)?\s*\])/
358 exists $acts->{$2} ? $templater->perform($acts, $2, $3)
359 : (++$not_found, $1)/ge;
363 return "<:arithmetic $arg:>";
366 return "<:arithmetic $prefix: $arg:>";
370 # this may be made more restrictive
371 my $result = eval $arg;
374 print STDERR "code generated by arithmetic: >>$arg<<\n";
375 return escape_html("** arithmetic error ".$@." **");
378 if ($prefix eq 'i') {
379 $result = int($result);
381 elsif ($prefix eq 'r') {
382 $result = sprintf("%.0f", $result);
384 elsif ($prefix =~ /^d(\d+)$/) {
385 $result = sprintf("%.*f", $1, $result);
388 return escape_html($result);
392 my ($cfg, $arg, $acts, $name, $templater) = @_;
393 my ($func, $args) = split ' ', $arg, 2;
395 $args = '' unless defined $args;
396 exists $acts->{$func}
397 or return "<: nobodytext $func $args :>";
398 my $value = $templater->perform($acts, $func, $args);
402 $value = decode_entities($value);
405 my $gen = Generate->new(cfg=>$cfg);
406 $gen->remove_block('Articles', $acts, \$value);
408 return escape_html($value);
412 my ($cgi, $args, $acts, $name, $templater) = @_;
414 my ($field, $func, $funcargs);
416 if ($args =~ /^(\[[^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*\])(.*)/) {
417 my ($fieldargs, $rest) = ($1, $2);
418 ($field) = DevHelp::Tags->get_parms($fieldargs, $acts, $templater);
419 defined $rest or $rest = '';
420 ($func, $funcargs) = split ' ', $rest, 2;
423 ($field, $func, $funcargs) = split ' ', $args, 3;
426 my $value = $cgi->param($field);
427 if (defined $value) {
428 return escape_html($value);
431 return '' unless $func && exists $acts->{$func};
433 $value = $templater->perform($acts, $func, $funcargs);
434 defined $value or $value = '';
440 my ($cgi, $args, $acts, $name, $templater) = @_;
442 my ($field, $num, $func, @funcargs) =
443 DevHelp::Tags->get_parms($args, $acts, $templater);
445 my @values = $cgi->param($field);
446 if (@values && $num < @values) {
447 return escape_html($values[$num]);
450 return '' unless $func && exists $acts->{$func};
452 my $value = $templater->perform($acts, $func, "@funcargs");
453 defined $value or $value = '';
459 my ($class, $acts, $cgi, $cfg) = @_;
461 require BSE::Util::Iterate;
462 my $it = BSE::Util::Iterate->new;
465 $class->static($acts, $cfg),
473 my @value = $cgi->param($_[0]);
474 escape_html("@value");
476 old => [ \&tag_old, $cgi ],
477 oldi => [ \&tag_oldi, $cgi ],
478 $it->make_iterator(\&DevHelp::Tags::iter_get_repeat, 'repeat', 'repeats'),
479 dynreplace => \&tag_replace,
480 dyntoday => [ \&tag_today, $cfg ],
481 dynreport => [ \&tag_report, $cfg ],
482 ajax => [ \&tag_ajax_dynamic, $cfg ],
483 ifAjax => [ \&tag_ifAjax, $cfg ],
484 $it->make_iterator([ \&iter_cfgsection, $cfg ], 'dyncfgentry', 'dyncfgsection'),
489 my ($class, $req) = @_;
493 BSE::Util::Tags->basic(undef, $req->cgi, $req->cfg),
494 csrfp => [ \&tag_csrfp, $req ],
495 ifError => 0, # overridden elsewhere
501 Generate a token that can be used to prevent cross-site request
504 Takes a single argument, the action to be authenticated.
509 my ($req, $args) = @_;
512 or return "** missing required argument **";
514 my ($name, $type) = split ' ', $args;
518 my $token = $req->get_csrf_token($name);
520 $type eq "plain" and return $token;
521 $type eq "hidden" and return
522 qq(<input type="hidden" name="_csrfp" value="$token" />);
523 return "** unknown csrfp type $type **";
527 my ($class, $array, $single, $plural, $saveto) = @_;
532 "iterate_${plural}_reset" => sub { $index = -1 },
533 $single => sub { escape_html($array->[$index]{$_[0]}) },
534 "if\u$plural" => sub { @$array },
535 "${single}_index" => sub { $index },
541 "iterate_${plural}" =>
543 if (++$index < @$array) {
555 "iterate_${plural}" =>
556 sub { ++$index < @$array },
561 sub make_dependent_iterator {
562 my ($class, $base_index, $getdata, $single, $plural, $saveto) = @_;
569 "iterate_${plural}_reset" =>
571 if ($$base_index != $last_base) {
572 @data = $getdata->($$base_index);
573 $last_base = $$base_index;
577 $single => sub { escape_html($data[$index]{$_[0]}) },
580 if ($$base_index != $last_base) {
581 @data = $getdata->($$base_index);
582 $last_base = $$base_index;
586 "${single}_index" => sub { $index },
592 "iterate_${plural}" =>
594 if (++$index < @data) {
606 "iterate_${plural}" =>
607 sub { ++$index < @data },
612 sub make_multidependent_iterator {
613 my ($class, $base_indices, $getdata, $single, $plural, $saveto) = @_;
615 # $base_indicies is an arrayref containing scalar refs
621 "iterate_${plural}_reset" =>
623 if (join(",", map $$_, @$base_indices) ne join(",", @last_bases)) {
624 @last_bases = map $$_, @$base_indices;
625 @data = $getdata->(@last_bases);
629 $single => sub { escape_html($data[$index]{$_[0]}) },
632 if (join(",", map $$_, @$base_indices) ne join(",", @last_bases)) {
633 @last_bases = map $$_, @$base_indices;
634 @data = $getdata->(@last_bases);
638 "${single}_index" => sub { $index },
644 "iterate_${plural}" =>
646 if (++$index < @data) {
658 "iterate_${plural}" =>
659 sub { ++$index < @data },
665 my ($class, $acts, $cfg, $req) = @_;
667 my $oit = BSE::Util::Iterate::Objects->new(cfg => $cfg);
670 help => [ \&tag_help, $cfg, 'admin' ],
673 single => "auditentry",
674 plural => "auditlog",
675 code => [ iter_auditlog => $class, $req ],
683 template => 'admin/helpicon',
684 prefix => '/admin/help/',
687 template => 'helpicon',
693 my ($cfg, $style, $args) = @_;
695 my ($name, $default) = split ' ', $args, 2;
697 return $cfg->entry("help style $style", $name, $default);
701 my ($class, $req, $args, $acts, $funcname, $templater) = @_;
703 my (@args) = DevHelp::Tags->get_parms($args, $acts, $templater);
704 require BSE::TB::AuditLog;
705 return sort { $b->id cmp $a->id }
706 BSE::TB::AuditLog->getBy(@args);
710 my ($cfg, $defstyle, $args) = @_;
712 my ($file, $entry, $style) = split ' ', $args;
714 $style ||= $defstyle;
716 my $template = $cfg->entry("help style $style", 'template')
717 || $cfg->entry("help style $defstyle", 'template')
718 || $help_styles{$style}{template}
719 || $help_styles{$defstyle}{template};
720 my $prefix = $cfg->entry("help style $style", 'prefix')
721 || $cfg->entry("help style $defstyle", 'prefix')
722 || $help_styles{$defstyle}{prefix}
723 || $help_styles{$defstyle}{prefix};
724 require BSE::Template;
731 stylecfg => [ \&tag_stylecfg, $cfg, $style ],
734 return BSE::Template->get_page($template, $cfg, \%acts,
735 $help_styles{$defstyle}{template});
738 my %dummy_site_article =
745 sub tag_if_user_can {
746 my ($req, $rperms, $args, $acts, $funcname, $templater) = @_;
748 my $debug = $req->cfg->entry('debug', 'ifUserCan', 0);
750 and print STDERR "Handling ifUserCan $args:\n";
751 my @checks = split /,/, $args;
752 for my $check (@checks) {
753 my ($perm, $artname) = split /:/, $check, 2;
755 and print STDERR " Perm: '$perm'\n";
758 if ($artname =~ /^\[/) {
759 my ($workname) = DevHelp::Tags->get_parms($artname, $acts, $templater);
761 print STDERR "Could not translate '$artname'\n";
764 $artname = $workname;
766 if ($artname =~ /^(-1|\d+)$/) {
767 if ($artname == -1) {
774 elsif ($artname =~ /^\w+$/) {
775 $article = $req->get_object($artname);
777 if (my $artid = $req->cfg->entry('articles', $artname)) {
780 elsif ($acts->{$artname}) {
781 $article = $templater->perform($acts, $artname, 'id');
784 #print STDERR "Unknown article name $artname\n";
785 die "ENOIMPL: Unknown article name\n";
794 # whew, so we should have an article
795 $req->user_can($perm, $article)
803 my ($req, $field) = @_;
805 my $user = $req->user
807 my $value = $user->{$field};
808 defined $value or $value = '';
810 return encode_entities($value);
814 my ($class, $req) = @_;
819 ifUserCan => [ \&tag_if_user_can, $req, \$perms ],
820 ifFormLogon => $req->session->{adminuserid},
821 ifLoggedOn => [ \&tag_if_logged_on, $req ],
822 adminuser => [ \&tag_admin_user, $req ],
827 my ($cfg, $errors, $args, $acts, $func, $templater) = @_;
829 my ($arg, $num) = DevHelp::Tags->get_parms($args, $acts, $templater);
830 #print STDERR "name $arg num $num\n";
831 return '' unless $errors->{$arg};
832 my $msg = $errors->{$arg};
833 if (ref $errors->{$arg}) {
835 return '' unless @$msg > $num && $msg->[$num];
838 my $images_uri = $cfg->entry('uri', 'images', '/images');
839 my $image = $cfg->entry('error_img', 'image', "$images_uri/admin/error.gif");
840 my $width = $cfg->entry('error_img', 'width', 16);
841 my $height = $cfg->entry('error_img', 'height', 16);
842 my $encoded = escape_html($msg);
843 return qq!<img src="$image" alt="$encoded" title="$encoded" width="$width" height="$height" border="0" align="top" />!;
847 my ($arg, $acts, $name, $templater) = @_;
848 my ($str, $re, $with, $global)
849 = DevHelp::Tags->get_parms($arg, $acts, $templater);
850 $re or return '** no regexp supplied to match **';
851 defined $with or $with = '$1';
856 my @out = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
857 defined or $_ = '' for @out;
860 $tmp =~ s/\$([1-9\$])/
861 $1 eq '$' ? '$' : $out[$1-1] /ge;
870 my @out = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
871 defined or $_ = '' for @out;
874 $tmp =~ s/\$([1-9\$])/
875 $1 eq '$' ? '$' : $out[$1-1] /ge;
885 my ($hash, $args) = @_;
887 my $value = $hash->{$args};
888 defined $value or $value = '';
889 if ($value =~ /\cJ/ && $value =~ /\cM/) {
897 my ($object, $args) = @_;
899 my $value = $object->{$args};
900 defined $value or $value = '';
901 if ($value =~ /\cJ/ && $value =~ /\cM/) {
904 escape_html($value, '<>&"');
908 my ($hash, $args) = @_;
910 my $value = $hash->{$args};
911 defined $value or $value = '';
917 my ($object, $args, $acts, $func) = @_;
919 $object or return '';
922 or return "** $func has no method $args **";
924 my $value = $object->$args();
925 defined $value or return "";
927 return escape_html($value);
931 my ($cfg, $args) = @_;
933 $args =~ s/^"(.+)"$/$1/;
935 $args ||= "%d-%b-%Y";
937 return bse_strftime($cfg, $args, localtime);
941 my ($cfg, $args, $acts, $tag_name, $templater) = @_;
943 my ($rep_name, $template, @args) =
944 DevHelp::Tags->get_parms($args, $acts, $templater);
946 or return "** no report name supplied to $tag_name tag **";
949 my $reports = BSE::Report->new($cfg);
950 my $report = $reports->load($rep_name, undef, BSE::DB->single);
952 or return "** could not load report '$rep_name' **";
954 # this will get embedded and normal tag replacement will then
955 # operate on it, no need to include basic/static tags
961 $reports->show_tags($rep_name, BSE::DB->single, \$msg, @args),
965 and return "** error in $tag_name: $msg **";
967 if (!defined $template or $template eq '-') {
968 $template = $reports->show_template($rep_name) || 'admin/reports/show1';
971 my $html = BSE::Template->get_source($template, $cfg);
972 if ($html =~ /<:\s*embed\s*start\s*:>(.*)<:\s*embed\s*end\s*:>/s
973 || $html =~ m"<\s*body[^>]*>(.*)<\s*/\s*body>"s) {
977 return BSE::Template->replace($html, $cfg, \%acts);
984 unless $cfg->entry('basic', 'ajax', 0);
987 if $cfg->entry('basic', 'allajax', 0);
989 my $ua = $ENV{HTTP_USER_AGENT};
990 defined $ua or $ua = ''; # some clients don\'t send a UA # silly cperl
992 my %fail_entries = $cfg->entries('nonajax user agents');
993 for my $re (values %fail_entries) {
998 my %entries = $cfg->entries('ajax user agents');
999 for my $re (values %entries) {
1010 return _if_ajax($cfg) ? 1 : 0;
1013 sub tag_ajax_dynamic {
1014 my ($cfg, $args, $acts, $tag_name, $templater) = @_;
1016 return '' unless _if_ajax($cfg);
1018 return tag_ajax($cfg, $args, $acts, $tag_name, $templater);
1022 my ($cfg, $args, $acts, $tag_name, $templater) = @_;
1024 my ($name, $arg_rest) = split ' ', $args, 2;
1026 my $defn = $cfg->entry('ajax definitions', $name)
1027 or return "** unknown ajax definition $name **";
1028 my ($type, $rest) = split ':', $defn, 2;
1030 defined $arg_rest or $arg_rest = '';
1032 if ($type eq 'inline') {
1033 # just replace $1, $2, etc in the rest of the text
1034 my @args = DevHelp::Tags->get_parms($arg_rest, $acts, $templater);
1037 $macro =~ s/(\$([1-9\$]))/
1038 $2 eq '$' ? '$' : $2 <= @args
1039 ? die "** not enough parameters for ajax definition $name **\n" : $args[$1-1]/xge;
1046 return "** invalid type $type for ajax definition $name **";
1051 my ($article, $cfg, $args) = @_;
1053 return escape_html(tag_article_plain($article, $cfg, $args));
1056 sub tag_article_plain {
1057 my ($article, $cfg, $args) = @_;
1061 && ref($article) ne 'HASH') {
1062 $value = $article->link($cfg);
1065 $value = $article->{$args};
1066 defined $value or $value = '';
1067 if ($value =~ /\cJ/ && $value =~ /\cM/) {
1068 $value =~ tr/\cM//d;
1076 my ($args, $acts, $tagname, $templater) = @_;
1078 my ($format, $value) =
1079 DevHelp::Tags->get_parms($args, $acts, $templater);
1080 $format or return "* no number format *";
1081 my $section = "number $format";
1082 my $cfg = BSE::Cfg->single;
1083 my $comma_sep = $cfg->entry($section, "comma", ",");
1084 $comma_sep =~ s/^"(.*)"$/$1/;
1085 $comma_sep =~ /\w/ and return "* comma cannot be a word character *";
1086 my $comma_limit = $cfg->entry($section, "comma_limit", 1000);
1087 my $commify = $cfg->entry($section, "commify", 1);
1088 my $dec_sep = $cfg->entry($section, "decimal", ".");
1089 my $div = $cfg->entry($section, "divisor", 1)
1090 or return "* divisor must be non-zero *";
1091 my $places = $cfg->entry($section, "places", -1);
1093 my $div_value = $value / $div;
1094 my $formatted = $places < 0 ? $div_value : sprintf("%.*f", $places, $div_value);
1096 my ($int, $frac) = split /\./, $formatted;
1097 if ($commify && $int >= $comma_limit) {
1098 1 while $int =~ s/([0-9])([0-9][0-9][0-9]\b)/$1$comma_sep$2/;
1101 if (defined $frac) {
1102 return $int . $dec_sep . $frac;