# Can StringTemplate be extended to offer printf-like formatting capabilities?

Apparently, Mathematica has no real sprintf-equivalent (unlike any other high-level language known to man). This has been asked before, but I'm wondering if the new StringTemplate function in Mathematica 10 can be extended to include such formatting capabilities.

What I have in mind is a function that takes a TemplateObject, and looks for "formatting specification strings" immediately after TemplateSlot's and TemplateExpression's and replaces them with TemplateExpression's containing appropriate formatting code. So, for example, you could write:

st = applyFormat@StringTemplate["Number: 1%.2 some other text"]


and you would get something equivalent to:

TemplateObject[{"Number: ",
TemplateExpression[ToString[NumberForm[TemplateSlot[1], {\[Infinity], 2}]]],
" some other text"}, InsertionFunction -> TextString,
CombinerFunction -> StringJoin]


I'm not particularly picky about the syntax (it doesn't have to mimic sprintf), as long as:

• it's easy to write and easy to read
• it supports Mathematica's number formatting functions (AccountingForm, ScientificForm...)
• it's extensible (e.g by delegating the formatting to a pattern that can be overwritten/extended)
• it's compatible with existing StringTemplate templates

I've started a function that does this, but I'm curious if you have better ideas (both implementation- and syntax-wise), so I'm posting it as an answer, not as part of the question.

-
Concerning "unlike any other high-level language known to man". I'm a man and I know a few high-level languages that have nothing like printf and are better off for it. For example, Smalltalk, Eiffel, and Scheme. The problem with printf and its ilk is that they are very bug friendly. The language compiler has no way to validate string templates. You only get error message from bad templates at run time. – m_goldberg Jul 11 '14 at 17:09
@m_goldberg: Maybe that sentence was a bit snarky ;-) Anyway, I can see the reasoning of offering no string template functionality, at least for a compiled language. (I'm not sure how you would write a large-scale application that can be localized to a dozen languages, but that's not the point here.) But MMA has string templates. It's just missing formatting options. As far as I can see, that doesn't make it safer, only harder to use. – nikie Jul 11 '14 at 20:00

I do want to support something like this with some custom syntax, though I can't guarantee when that will actually happen.

In the meantime, you can do this:

StringTemplate["Pi is <*NumberForm[N[#],3]*>!"][Pi]


which produces:

"Pi is 3.14!"


Unfortunately TextString isn't that smart yet about NumberForm, and doesn't support EngineeringForm and ScientificForm at all, though it will do that in a coming point release.

At least TextString gets us away from the braindead default behavior of OutputForm in ToString. ToString is basically broken for producing human-readable strings.

-

This is my first attempt:

Clear[formatPattern, formatPatternQ, applyFormat, applyFormatToValue, \
builtinFormatFunction]
formatPattern =
StartOfString ~~ "%" ~~
format : (DigitCharacter ... ~~ ("." ~~ DigitCharacter ..) ...) ~~
formatName : WordCharacter ... ~~ rest : ___;
formatPatternQ = StringMatchQ[#, formatPattern] &;

applyFormat[t_] := t
applyFormat[
TemplateObject[{b___, slot_TemplateSlot | TemplateExpression[slot_],
fmtStr_?formatPatternQ, r___}, opts : ___]] :=
Module[{formatting, restStr},
{{formatting, restStr}} =
StringCases[fmtStr,
formatPattern :> {applyFormatToValue[
ToExpression /@ StringSplit[format, ".", All], formatName],
rest}];
applyFormat[
TemplateObject[{b, TemplateExpression[formatting[slot]], restStr,
r}, opts]]]

slot_] :=
applyFormatToValue[{Null, decimalPlaces_}, format_][slot_] :=
Module[{formatFunction, value},
formatFunction = builtinFormatFunction[format];
value = N@slot;
ToString[formatFunction[value, {\[Infinity], decimalPlaces}],

builtinFormatFunction["sci"] := ScientificForm
builtinFormatFunction["eng"] := EngineeringForm
builtinFormatFunction["num" | ""] := NumberForm
builtinFormatFunction["acc"] := AccountingForm

(* not yet implemented - formatted values are in TraditionalForm, I
don't know how to format those *)


Usage:

applyFormat[StringTemplate["abc 1%.3 def"]][π*10000]


Out[27]= abc 31415.927 def

applyFormat[StringTemplate["abc 1%.3sci def"]][π*10000]


Out[28]= abc 3.142*10^4 def

applyFormat[StringTemplate["abc 1%5.3eng def"]][π*10000]


Out[29]= abc 31.416*10^3 def

-

You can also try:

StringTemplate["Number: 1 some other text"]
[ToString[NumberForm[N[Pi], {\[Infinity], 2}]]]


That is, leave the formatting to ToString, which does handle NumberForm and everything else.

-