Nasser Nasser - 3 months ago 6
Perl Question

Is it possible to apply sprintf to format a HERE DOCUMENT string?

I'd like to embed tokens inside a HERE DOCUMENT and then use

(s)printf
on the final string to replace these tokens with actual values. The reason is: This makes it much easier to build a HERE DOCUMENT with some parts of it that are varying.

To explain, here is how I would do this in Python. From the small example below it will be more clear what I am trying to do. In Python
r"""
is similar to Perl
<<EOT
, i.e. it builds multi-line raw string. The string has to end with
"""
similar to Perl ending with
EOT
.

s=r"""
\documentclass[12pt,titlepage]{article}
\begin{document}
\title{%(title)s}
\date{%(date)s}
some text
\end{document}"""

print s % {"title": "My report","date": "1/1/2016"}


The above prints

\documentclass[12pt,titlepage]{article}
\begin{document}
\title{My report}
\date{1/1/2016}
some text
\end{document}


Notice that the
%(title)
and
%(date)
were replaced at the end by values given. This is very similar to doing
sprintf('%s',some_string)
, but here it is applied to the raw string itself. This is very handy and makes building raw string very easy.

Here is an MWE in Perl, but I am stuck on the last part, and I also do not know how to do the whole thing:

#!/usr/bin/perl -w
use strict;
use warnings;
my $title = "My report";
my $date = "1/1/2016";

my $s =<<'EOT';
\documentclass[12pt,titlepage]{article}
\begin{document}
\title{%(title)s}
\date{%(date)s}
some text
\end{document}
EOT

print($s); #how to format this in order to replace %title and %date
#emmbeded in $s with the values above? is it possible?


Make sure
EOT
is leftmost and has no spaces after it.

I do not know if this is even possible in Perl. But I think if it is possible in Python, it should also be possible in Perl?

Please note that I can't use interpolation in HERE DOCUMENT, i.e.
<<"EOT"
will not work for many reasons, and must use non interpolation HERE DOCUMENT. So something like this will not work for me:

my $s =<<"EOT";
\documentclass[12pt,titlepage]{article}
\begin{document}
\title{$title}
\date{$date}
some text
\end{document}
EOT

Answer

To answer the question, there are a few options; which I'll only address two of the simpler methods to implement (i.e., printf/sprintf and regex).

  1. printf/sprintf

    If you know the order of the placeholders, you can use the correct templating variables %s for string and insert your values using variables in the printf call.

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    my $title = "My report";
    my $date  = "1/1/2016";
    
    my $s =<<'EOT';
    \documentclass[12pt,titlepage]{article}
    \begin{document}
    \title{%s}
    \date{%s}
      some text
    \end{document}
    EOT
    
    printf( $s, $title, $date);
    
  2. Regex

    Using regex, you could look for the literal string and replace that with some desired value (a variable).

    Simple Replace

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    my $title = "My report";
    my $date  = "1/1/2016";
    
    my $s     = <<'EOT';
    \documentclass[12pt,titlepage]{article}
    \begin{document}
    \title{$title}
    \date{$date}
      some text
    \end{document}
    EOT
    
    $s =~ s/\$(title|date)/"\$$1"/eeg;            # ee is important and so is wrapping the replacement pattern in a string
    
    print $s;
    

    Hash Table Replace

    You could make the process even more automated/flexible by using a replace hash. This helps to automatically generate the regex pattern and use the hash table to retrieve the respective value for the replacement. The benefit is you could add more-or-less variables to your hash, without having to touch the regex again (the pattern doesn't get any larger).

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    my $replace = {
      title => q{My report},
      date  => q{1/1/2016}
    };
    
    my $s = <<'EOT';
    \documentclass[12pt,titlepage]{article}
    \begin{document}
    \title{$title}
    \date{$date}
      some text
    \end{document}
    EOT
    
    $s =~ s/\$(@{[ join '|', keys %$replace ]})/$replace->{$1}/g;
    
    print $s;
    

    Regex Caveats

    There are however many caveats. For instance, word-wrapping could be a problem depending on where the variable breaks - the regex is not set up to look for that. A bigger concern is if you actually wanted the literal string '$title' or '$date' to appear in the generated document (after executing the TeX code), the regex would have replaced it with some value. You'll have to determine which works best for you.

    Generally, in templating/parsing there isn't a one-line answer and it usually involves multiple statement process for various reasons (e.g., performance, maintainability, logic/complexity, or simply because it couldn't work otherwise).

Comments