Does Perl support named parameters in function calls? - perl

In my experience of languages that support the feature, programs that call functions with named parameters rather than positional parameters are easier to read and maintain.
I think Perl has this feature, but it's not working for me.
Is it a quirk of the package I'm using, or am I doing it wrong?
Setting up the function call
My fist Perl project is to use the HTML::TableExtract package to extract table data from HTML markup and display it as text.
The following code sets up the parser:
use strict;
use warnings;
use HTML::TableExtract;
my $markup = <<MARKUP;
<table>
<tr> <th>a</th> <th>b</th> <th>c</th> </tr>
<tr> <td>1</td> <td>2</td> <td>3</td> </tr>
<tr> <td>4</td> <td>5</td> <td>6</td> </tr>
</table>
MARKUP
my $parser = HTML::TableExtract->new() ;
$parser->parse($markup) ;
The documentation says that I can dump output to the Command Prompt using the tables_dump method and use parameters $show_content and $col_sep to control the output format:
tables_report([$show_content, $col_sep])
Return a string summarizing extracted tables, along with their depth and count. Optionally takes a $show_content flag which will dump the extracted contents of each table as well with columns separated by $col_sep. Default $col_sep is ':'.
tables_dump([$show_content, $col_sep])
Same as tables_report() except dump the information to STDOUT.
Calling with positional and with named parameters
If I pass positional parameters in documentation order, I get the output I expect:
$parser->tables_dump(1, '_') ;
The columns are separated by an underscore instead of the default colon:
TABLE(0, 0):
a_b_c
1_2_3
4_5_6
Following Perl.com's Advance Subroutines article, I tried to pass a hash containing parameter names and values to clarify the meaning of the parameters:
$parser->tables_dump({show_content => 1, col_sep => '_'}) ;
Perl doesn't understand this. It ignores the value of col_sep and outputs with the default value:
TABLE(0, 0):
a:b:c
1:2:3
4:5:6
I get the same output if I don't try to change the separator:
$parser->tables_dump({show_content => 1}) ;
Even if I specify nonsense parameter names, I get the same output:
$parser->tables_dump({tweedledum => 1, tweedledee => '_'}) ;
Can I call this function using the named parameter style, or should I just settle for positional?

Perl doesn't natively support named parameters, however it's possible to design functions to accept named parameters (as a hash or hashref). It's up to the author of the function as to how they implement it. You'll need to supply arguments the function is expecting or you'll get unexpected results.

Named parameter passing in Perl (even with default values) is explained very well in Object Oriented Perl, Chapter 6.
This style is very important and is widely used in object constructors. That's why it is explained in their OO Perl book.
I will quote two of their examples:
# This is how you call a subroutine using named argument passing
interests(name => "Paul", language => "Perl", favourite_show => "Buffy");
# This is how you define the subroutine to accept named arguments
sub interests {
my (%args) = #_;
# This is how you capture named arguments and define
# defaults for the ones missing from a particular call.
my $name = $args{name} || "Bob the Builder";
my $language = $args{language} || "none that we know";
my $favourite_show = $args{favourite_show} || "the ABC News";
print "${name}’s primary language is $language. " .
"$name spends their free time watching $favourite_show\n";
}
Another example that gives a different way of defining defaults (in a hash) is this:
my %defaults = ( pager => "/usr/bin/less", editor => "/usr/bin/vim" );
sub set_editing_tools {
my (%args) = #_;
# Here we join our arguments with our defaults. Since when
# building a hash it’s only the last occurrence of a key that
# matters, our arguments will override our defaults.
%args = (%defaults, %args);
# print out the pager:
print "The new text pager is: $args{pager}\n";
# print out the editor:
print "The new text editor is: $args{editor}\n";
}

Following Perl.com's Advance Subroutines article, I tried to pass a
hash containing parameter names and values to clarify the meaning of
the parameters:
That article covers a way to write subroutines such that they will accept a hashref of named parameters. If you're calling a sub that isn't written to accept that, then it won't know how to handle it correctly.
$parser->tables_dump({show_content => 1, col_sep => '_'}) ;
Perl doesn't understand this. It ignores the value of col_sep and
outputs with the default value:
Not to be overly pedantic, but Perl understands that just fine. However, tables_dump is only written to accept a list of scalar parameters. When you call it that way, it receives a single scalar parameter. This parameter just happens to be a reference to a hash, but tables_dump doesn't know or care about that, so it uses the reference as the value of $show_content. This is probably equivalent to passing 1 for show_content, since both 1 and any possible reference will evaluate as "true" in boolean context and I assume that $show_content is only ever used as a boolean.
Since there is no second parameter, nothing gets assigned to $col_sep, so it uses the default separator, as you observed.

Perl has no built-in support for named parameters. If you want to use them, the function has to be specifically written to accept arguments in that style. So you'll have to settle for positional arguments (or write a wrapper function (probably in a subclass)).

It's not too complicated. You pass the key-value pair like a hash or as a hash reference, and in the subroutine you load the arguments into a hash or hash reference.
# called like $parser->tables_dump({show_content => 1, col_sep => '_'}) ;
sub TheParser::tables_dump {
my ($self, $args) = #_;
if ($args->{show_content} == 1) {
print join $args->{col_sep}, $self->the_column_data();
...
}
}
With another line, you can load the named arguments you know about into appropriately named variables:
my ($self, $args) = #_;
my ($show_content, $col_sep) = #$args{qw(show_content col_sep)};
if ($show_content == 1) {
...

Going off just the title's question, you could do something like the below:
use strict;
use Carp::Assert;
sub func {
my (%hash) = #_;
assert($hash{'baz'} == 1);
assert($hash{'mu'} == 2);
}
func('baz' => 1, 'mu' => 2);

Here is a simple program I wrote that can use a type of named parameters. It allows for default values.
#!/usr/bin/perl
use strict;
use warnings;
use 5.014;
use POSIX qw/ strftime /;
# Script to get prior Monday (if today is Mon, then Mon a week ago).
for my $day (qw/ Sun Mon Tue Wed Thu Fri Sat /) {
say "Last $day: ", last_monday( day => $day );
}
sub last_monday {
my %arg = ( time => [localtime],
day => 'mon',
span => 1,
#_
);
my $dow; # day of week
if ('sunday' =~ /$arg{day}/i) { $dow = 0}
elsif ('monday' =~ /$arg{day}/i) { $dow = 1}
elsif ('tuesday' =~ /$arg{day}/i) { $dow = 2}
elsif ('wednesday' =~ /$arg{day}/i) { $dow = 3}
elsif ('thursday' =~ /$arg{day}/i) { $dow = 4}
elsif ('friday' =~ /$arg{day}/i) { $dow = 5}
elsif ('saturday' =~ /$arg{day}/i) { $dow = 6}
else {
warn "$arg{day} is not a valid day of week. $!";
return;
}
my ($wday, #dmy) = #{ $arg{time} }[6, 3..5];
# (will work across month, year boundries)
$dmy[0] -= ($wday - $dow) % 7 || ($arg{span} ? 7 : 0); # $dmy[0] == mday
return strftime "%Y%m%d", 0,0,0,#dmy;
}

Related

Perl dereference on args cannot be used in comparison, but copy of it can

I have read some arguments at the command line:
#!/usr/bin/env perl
use 5.0360;
use warnings FATAL => 'all';
use autodie ':default';
use Devel::Confess 'color';
use Getopt::ArgParse;
my $parser = Getopt::ArgParse->new_parser(
help => 'This script finds and pretty-prints a line within a CSV file',
);
$parser-> add_args(
['-file', '-f', required => 1, type => 'Scalar'],
['-regex', '-r', required => 1, type => 'Scalar'], # regex pattern within each line
['-sep' , '-s', required => 0, type => 'Scalar']
);
my $args = $parser->parse_args(#ARGV);
my $sep = $args->sep // ',';
my $regex = $args->regex;
my #header;
say $args->regex;
open my $fh, '<', $args->file;
while (<$fh>) {
chomp;
if ($. == 1) {
#header = split $sep;
next
}
next unless /$regex/; # key point
say __LINE__;
# code later
}
the problem here is that the regex comparison
next unless /$regex/
works, but
next unless /$args->regex/
does not work, even though the two are copies of each other via my $regex = $args->regex;
Strangely, I can use $args->file but cannot use $args->regex
Why does $args->regex work like this? What is the correct terminology for $args->regex? It's not a hash, it's some sort of object.
You can't interpolate method calls into a string. I guarantee you that $args->file doesn't work either here. Note that $args->regex is not a field access but a method call, equivalent to $args->regex().
When interpolating a variable into a quote-like context (including regexes), you can use scalars, arrays, and array/hash field accesses.
I think that using a named variable my $regex = $args->regex is the clearest solution here. But if you really want to interpolate arbitrary expressions into a string, you can use a trick: you can interpolate arrays #foo, thus also an array dereference #{ ... }. Then:
next unless /#{[ $args->regex ]}/;

Retain quotes on CSV fields that were quoted in the input

I have a CSV file such that a few of the fields are quoted regardless of whether they need to be. What I wish to do is load this file, modify a few of the values, and produce the modified CSV with the quoted fields intact.
I'm currently using Perl's Text::CSV package to attempt to solve this problem, but have ran into a bit of a roadblock. The following is a small test script to demonstrate the problem:
use Text::CSV;
my $csv = Text::CSV->new ({'binary' => 1, 'allow_loose_quotes' => 1, 'keep_meta_info' => 1});
my $line = q^hello,"world"^;
print qq^input: $line\n^;
$csv->parse($line);
my #flds = $csv->fields();
$csv->combine(#flds);
print 'output: ', $csv->string(), "\n";
produces:
input: hello,"world"
output: hello,world
According to Text::CSV's documentation, an is_quoted() function exists to test if a field had been quoted in the input, but if I use this to add surrounding quotes to a field, I get unexpected results:
my $csv = Text::CSV->new ({'binary' => 1, 'allow_loose_quotes' => 1, 'keep_meta_info' => 1});
my $line = q^hello,"world"^;
print qq^input: $line\n^;
$csv->parse($line);
my #flds = $csv->fields();
for my $idx (0..$#flds) {
if ($csv->is_quoted($idx)) {
$flds[$idx] = qq^"$flds[$idx]"^;
}
}
$csv->combine(#flds);
print 'output: ', $csv->string(), "\n";
Producing:
input: hello,"world"
output: hello,"""world"""
where I believe the quotes I've added before the combine() are being seen as part of the field, and so are being escaped with a second double quote as combine() is processing.
What would be the best way to ensure quoted fields are left intact from input to output? I'm not certain the application will accept always_quote'ed fields... Is there some combination of Text::CSV object attributes that will allow for keeping quotes intact? Or perhaps am I left with adjusting the record post-combine?
It's a shame but it appears that while keep_meta_info gives you access to the metadata there's no option to tell Text::CSV to reapply the is_quoted state on output.
Depending on how complex your record is you could just reassemble it yourself. But then you'd have to cope with changes to string fields that were previously safely unquoted but after your processing now require quotes. That will depend on the types of changes you introduce, i.e. whether or not you ever expect that a previously "safe" string value will become unsafe. If the answer is "never" (i.e. 0.00000% chance), then you should just do the reassembly yourself and document what you've done.
Post-processing would require that you CSV-parse the string to handle the possibility of commas and other unsafe characters inside strings, so that may not be an option.
Or, you could dive into the code for Text::CSV and implement the desired functionality. I.e. allow the user to force quoting of a specific field on output. I played around with it, and it looks like part of the required mechanism might be in place but unfortunately all I have access to is the XS version, which delegates to native code, so I can't delve deeper at this time. This is as far as I got:
Original combine method. Note the setting of _FFLAGS to undef.
sub combine
{
my $self = shift;
my $str = "";
$self->{_FIELDS} = \#_;
$self->{_FFLAGS} = undef;
$self->{_STATUS} = (#_ > 0) && $self->Combine (\$str, \#_, 0);
$self->{_STRING} = \$str;
$self->{_STATUS};
} # combine
My attempt. I guessed that the second argument to Combine might be the flags, but since the (lowercase) combine API is based on receiving an array and not an arrayref, there's no way to pass two arrays in. I changed it to expect two arrayrefs and tried passing the second to Combine but that failed with "Can't call method "print" on unblessed reference".
sub combine2
{
my $self = shift;
my $str = "";
my $f = shift;
my $g = shift;
$self->{_FIELDS} = $f;
$self->{_FFLAGS} = $g;
$self->{_STATUS} = (#$f > 0) && $self->Combine (\$str, $f, $g);
$self->{_STRING} = \$str;
$self->{_STATUS};
} # combine

Where does a Perl subroutine get values missing from the actual parameters?

I came across the following Perl subroutine get_billable_pages while chasing a bug. It takes 12 arguments.
sub get_billable_pages {
my ($dbc,
$bill_pages, $page_count, $cover_page_count,
$domain_det_page, $bill_cover_page, $virtual_page_billing,
$job, $bsj, $xqn,
$direction, $attempt,
) = #_;
my $billable_pages = 0;
if ($virtual_page_billing) {
my #row;
### Below is testing on the existence of the 11th and 12th parameters ###
if ( length($direction) && length($attempt) ) {
$dbc->xdb_execute("
SELECT convert(int, value)
FROM job_attribute_detail_atmp_tbl
WHERE job = $job
AND billing_sub_job = $bsj
AND xqn = $xqn
AND direction = '$direction'
AND attempt = $attempt
AND attribute = 1
");
}
else {
$dbc->xdb_execute("
SELECT convert(int, value)
FROM job_attribute_detail_tbl
WHERE job = $job
AND billing_sub_job = $bsj
AND xqn = $xqn
AND attribute = 1
");
}
$cnt = 0;
...;
But is sometimes called with only 10 arguments
$tmp_det = get_billable_pages(
$dbc2,
$row[6], $row[8], $row[7],
$domain_det_page, $bill_cover_page, $virtual_page_billing,
$job1, $bsj1, $row[3],
);
The function does a check on the 11th and 12th arguments.
What are the 11th and 12th arguments when the function is passed only 10 arguments?
Is it a bug to call the function with only 10 arguments because the 11th and 12th arguments end up being random values?
I am thinking this may be the source of the bug because the 12th argument had a funky value when the program failed.
I did not see another definition of the function which takes only 10 arguments.
The values are copied out of the parameter array #_ to the list of scalar variables.
If the array is shorter than the list, then the excess variables are set to undef. If the array is longer than the list, then excess array elements are ignored.
Note that the original array #_ is unmodified by the assignment. No values are created or lost, so it remains the definitive source of the actual parameters passed when the subroutine is called.
ikegami suggested that I should provide some Perl code to demonstrate the assignment of arrays to lists of scalars. Here is that Perl code, based mostly on his edit
use strict;
use warnings;
use Data::Dumper;
my $x = 44; # Make sure that we
my $y = 55; # know if they change
my #params = (8); # Make a dummy parameter array with only one value
($x, $y) = #params; # Copy as if this is were a subroutine
print Dumper $x, $y; # Let's see our parameters
print Dumper \#params; # And how the parameter array looks
output
$VAR1 = 8;
$VAR2 = undef;
$VAR1 = [ 8 ];
So both $x and $y are modified, but if there are insufficient values in the array then undef is used instead. It is as if the source array was extended indefinitely with undef elements.
Now let's look at the logic of the Perl code. undef evaluates as false for the purposes of conditional tests, but you apply the length operator like this
if ( length($direction) && length($attempt) ) { ... }
If you have use warnings in place as you should, Perl would normally produce a Use of uninitialized value warning. However length is unusual in that, if you ask for the length of an undef value (and you are running version 12 or later of Perl 5) it will just return undef instead of warning you.
Regarding "I did not see another definition of the function which takes only 10 arguments", Perl doesn't have function templates like C++ and Java - it is up to the code in the subroutine to look at what it has been passed and behave accordingly.
No, it's not a bug. The remaining arguments are "undef" and you can check for this situation
sub foo {
my ($x, $y) = #_;
print " x is undef\n" unless defined $x;
print " y is undef\n" unless defined $y;
}
foo(1);
prints
y is undef

Perl - How to create commands that users can input in console?

I'm just starting in Perl and I'm quite enjoying it. I'm writing some basic functions, but what I really want to be able to do is to use those functions intelligently using console commands. For example, say I have a function adding two numbers. I'd want to be able to type in console "add 2, 4" and read the first word, then pass the two numbers as parameters in an "add" function. Essentially, I'm asking for help in creating some basic scripting using Perl ^^'.
I have some vague ideas about how I might do this in VB, but Perl, I have no idea where I'd start, or what functions would be useful to me. Is there something like VB.net's "Split" function where you can break down the contents of a scalar into an array? Is there a simple way to analyse one word at a time in a scalar, or iterate through a scalar until you hit a separator, for example?
I hope you can help, any suggestions are appreciated! Bear in mind, I'm no expert, I started Perl all of a few weeks ago, and I've only been doing VB.net half a year.
Thank you!
Edit: If you're not sure what to suggest and you know any simple/intuitive resources that might be of help, that would also be appreciated.
Its rather easy to make a script which dispatches to a command by name. Here is a simple example:
#!/usr/bin/env perl
use strict;
use warnings;
# take the command name off the #ARGV stack
my $command_name = shift;
# get a reference to the subroutine by name
my $command = __PACKAGE__->can($command_name) || die "Unknown command: $command_name\n";
# execute the command, using the rest of #ARGV as arguments
# and print the return with a trailing newline
print $command->(#ARGV);
print "\n";
sub add {
my ($x, $y) = #_;
return $x + $y;
}
sub subtract {
my ($x, $y) = #_;
return $x - $y;
}
This script (say its named myscript.pl) can be called like
$ ./myscript.pl add 2 3
or
$ ./myscript.pl subtract 2 3
Once you have played with that for a while, you might want to take it further and use a framework for this kind of thing. There are several available, like App::Cmd or you can take the logic shown above and modularize as you see fit.
You want to parse command line arguments. A space serves as the delimiter, so just do a ./add.pl 2 3 Something like this:
$num1=$ARGV[0];
$num2=$ARGV[1];
print $num1 + $num2;
will print 5
Here is a short implementation of a simple scripting language.
Each statement is exactly one line long, and has the following structure:
Statement = [<Var> =] <Command> [<Arg> ...]
# This is a regular grammar, so we don't need a complicated parser.
Tokens are seperated by whitespace. A command may take any number of arguments. These can either be the contents of variables $var, a string "foo", or a number (int or float).
As these are Perl scalars, there is no visible difference between strings and numbers.
Here is the preamble of the script:
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
strict and warnings are essential when learning Perl, else too much weird stuff would be possible. The use 5.010 is a minimum version, it also defines the say builtin (like a print but appends a newline).
Now we declare two global variables: The %env hash (table or dict) associates variable names with their values. %functions holds our builtin functions. The values are anonymous functions.
my %env;
my %functions = (
add => sub { $_[0] + $_[1] },
mul => sub { $_[0] * $_[1] },
say => sub { say $_[0] },
bye => sub { exit 0 },
);
Now comes our read-eval-loop (we don't print by default). The readline operator <> will read from the file specified as the first command line argument, or from STDIN if no filename is provided.
while (<>) {
next if /^\s*\#/; # jump comment lines
# parse the line. We get a destination $var, a $command, and any number of #args
my ($var, $command, #args) = parse($_);
# Execute the anonymous sub specified by $command with the #args
my $value = $functions{ $command }->(#args);
# Store the return value if a destination $var was specified
$env{ $var } = $value if defined $var;
}
That was fairly trivial. Now comes some parsing code. Perl “binds” regexes to strings with the =~ operator. Regexes may look like /foo/ or m/foo/. The /x flags allows us to include whitespace in our regex that doesn't match actual whitespace. The /g flag matches globally. This also enables the \G assertion. This is where the last successful match ended. The /c flag is important for this m//gc style parsing to consume one match at a time, and to prevent the position of the regex engine in out string to being reset.
sub parse {
my ($line) = #_; # get the $line, which is a argument
my ($var, $command, #args); # declare variables to be filled
# Test if this statement has a variable declaration
if ($line =~ m/\G\s* \$(\w+) \s*=\s* /xgc) {
$var = $1; # assign first capture if successful
}
# Parse the function of this statement.
if ($line =~ m/\G\s* (\w+) \s*/xgc) {
$command = $1;
# Test if the specified function exists in our %functions
if (not exists $functions{$command}) {
die "The command $command is not known\n";
}
} else {
die "Command required\n"; # Throw fatal exception on parse error.
}
# As long as our matches haven't consumed the whole string...
while (pos($line) < length($line)) {
# Try to match variables
if ($line =~ m/\G \$(\w+) \s*/xgc) {
die "The variable $1 does not exist\n" if not exists $env{$1};
push #args, $env{$1};
}
# Try to match strings
elsif ($line =~ m/\G "([^"]+)" \s*/xgc) {
push #args, $1;
}
# Try to match ints or floats
elsif ($line =~ m/\G (\d+ (?:\.\d+)? ) \s*/xgc) {
push #args, 0+$1;
}
# Throw error if nothing matched
else {
die "Didn't understand that line\n";
}
}
# return our -- now filled -- vars.
return $var, $command, #args;
}
Perl arrays can be handled like linked list: shift removes and returns the first element (pop does the same to the last element). push adds an element to the end, unshift to the beginning.
Out little programming language can execute simple programs like:
#!my_little_language
$a = mul 2 20
$b = add 0 2
$answer = add $a $b
say $answer
bye
If (1) our perl script is saved in my_little_language, set to be executable, and is in the system PATH, and (2) the above file in our little language saved as meaning_of_life.mll, and also set to be executable, then
$ ./meaning_of_life
should be able to run it.
Output is obviously 42. Note that our language doesn't yet have string manipulation or simple assignment to variables. Also, it would be nice to be able to call functions with the return value of other functions directly. This requires some sort of parens, or precedence mechanism. Also, the language requires better error reporting for batch processing (which it already supports).

Perl: Can't pass an "on-the-fly" array to a sub

strftime(), as per cpan.org:
print strftime($template, #lt);
I just can't figure the right Perl code recipe for this one. It keeps reporting an error where I call strftime():
...
use Date::Format;
...
sub parse_date {
if ($_[0]) {
$_[0] =~ /(\d{4})/;
my $y = $1;
$_[0] =~ s/\d{4}//;
$_[0] =~ /(\d\d)\D(\d\d)/;
return [$2,$1,$y];
}
return [7,7,2010];
}
foreach my $groupnode ($groupnodes->get_nodelist) {
my $groupname = $xp->find('name/text()', $groupnode);
my $entrynodes = $xp->find('entry', $groupnode);
for my $entrynode ($entrynodes->get_nodelist) {
...
my $date_added = parse_date($xp->find('date_added/text()', $entrynode));
...
$groups{$groupname}{$entryname} = {...,'date_added'=>$date_added,...};
...
}
}
...
my $imday = $maxmonth <= 12 ? 0 : 1;
...
while (my ($groupname, $entries) = each %groups) {
...
while (my ($entryname, $details) = each %$entries) {
...
my $d = #{$details->{'date_added'}};
$writer->dataElement("creation", strftime($date_template, (0,0,12,#$d[0^$imday],#$d[1^$imday]-1,#$d[2],0,0,0)));
}
...
}
...
If I use () to pass the required array by strftime(), I get:
Type of arg 2 to Date::Format::strftime must be array (not list) at ./blah.pl line 87, near "))"
If I use [] to pass the required array, I get:
Type of arg 2 to Date::Format::strftime must be array (not anonymous list ([])) at ./blah.pl line 87, near "])"
How can I pass an array on the fly to a sub in Perl? This can easily be done with PHP, Python, JS, etc. But I just can't figure it with Perl.
EDIT: I reduced the code to these few lines, and I still got the exact same problem:
#!/usr/bin/perl
use warnings;
use strict;
use Date::Format;
my #d = [7,13,2010];
my $imday = 1;
print strftime( q"%Y-%m-%dT12:00:00", (0,0,12,$d[0^$imday],$d[1^$imday]-1,$d[2],0,0,0));
Where an array is required and you have an ad hoc list, you need to actually create an array. It doesn't need to be a separate variable, you can do just:
strftime(
$date_template,
#{ [0,0,12,$d[0^$imday],$d[1^$imday],$d[2],0,0,0] }
);
I have no clue why Date::Format would subject you to this hideousness and not just expect multiple scalar parameters; seems senseless (and contrary to how other modules implement strftime). Graham Barr usually designs better interfaces than this. Maybe it dates from when prototypes still seemed like a cool idea for general purposes.
To use a list as an anonymous array for, say, string interpolation, you could write
print "#{[1, 2, 3]}\n";
to get
1 2 3
The same technique provides a workaround to Date::Format::strftime's funky prototype:
print strftime(q"%Y-%m-%dT12:00:00",
#{[0,0,12,$d[0^$imday],$d[1^$imday]-1,$d[2],0,0,0]});
Output:
1900-24709920-00T12:00:00
Normally, it is easy to pass arrays "on-the-fly" to Perl subroutines. But Date::Format::strftime is a special case with a special prototype ($\#;$) that doesn't allow "list" arguments or "list assignment" arguments:
strftime($format, (0,0,12,13,7-1,2010-1900)); # not ok
strftime($format, #a=(0,0,12,13,7-1,2010-1900)); # not ok
The workaround is that you must call strftime with an array variable.
my #time = (0,0,12,13,7-1,2010-1900); # note: #array = ( ... ), not [ ... ]
strftime($format, #time);
I looked again and I see the real problem in this code:
my $d = #{$details->{'date_added'}};
$writer->dataElement("creation", strftime($date_template, (0,0,12,#$d[0^$imday],#$d[1^$imday]-1,#$d[2],0,0,0)));
Specifically #{$details->{'date_added'}} is a dereference. But you're assigning it to a scalar variable and you don't need to dereference in the line below it:
my #d = #{$details->{'date_added'}};
$writer->dataElement("creation", strftime($date_template, (0,0,12,$d[0^$imday],$d[1^$imday]-1,$d[2],0,0,0)));
I've created a regular array for your reference #d and just accessed it as a regular array ( $d[ ... ] instead of #$d[ ... ] )