For any Velocity project, I've taken to offering clients a separate config token, call it {{my.ThisIsWhereYouChangeStuff}}
, where they can manage some output settings without having to email me all the time.
Then there are one or more {{my.PleaseDontChangeAnythingInHereItsFragile}}
tokens with the meat of the code.
Velocity #define
directives are really handy for the config token. They're a bit more fault-tolerant than #set
statements, where the non-technical person has to remember to escape quotes, close parentheses and such.
That is, instead of:
#set( $baseURL = "https://www.example.com/preferencecenter" )
#set( $linkText = "Say \u0022Hello\u0022 to our new preference center." )
I give them a token like so:
#define( $baseURL )
https://www.example.com/preferencecenter
#end
#define( $linkText )
Say "Hello" to our new preference center.
#end
As long as they leave the #define
/#end
lines alone they can change anything in-between (especially good for multiline text, as you might imagine).
There's a little trick to using #define
, though, and that is like everything in Velocity, it preserves whitespace. What whitespace, you may ask? Well, look at the end of this line:
https://www.example.com/preferencecenter
That has a carriage return + line feed (or just LF, depending on the OS) at the end.
So if I output a link like so:
<a href="${baseURL}">${linkText}</a>
The email will contain:
<a href="https://www.example.com/preferencecenter
">Say "Hello" to our new preference center.
</a>
Instead of what you intended:
<a href="https://www.example.com/preferencecenter">Say "Hello" to our new preference center.</a>
Which is bad because it will wreck your links — even though you may not even see the wreckage in Preview because of the way HTML itself swallows line breaks.
Now, you can suppress the trailing whitespace by adding a comment ##
at the end of the line…
https://www.example.com/preferencecenter##
… but I daresay that's not an improvement, since the idea is to offer this token as a not-too-fragile place for a non-technical person to make adjustments, and adding ##
is something they're bound to forget or mess up.
So what you want to do is let them enter text in as close to free-form fashion as possible. Then in your code, strip out extraneous whitespace at the beginning or end to be tolerant of minor messups.
How trim()
works
The documentation of the trim() method in Java, which exists on any Java String — and therefore on any Velocity String — is almost lovable in its complexity.
trim()
does exactly what we want, but you have to understand the ASCII table to know that! Not that a programmer shouldn't understand ASCII, but it's a particularly circuitous explanation IMO:
[L]et k be the index of the first character in the string whose code is greater than '\u0020' (the space character), and let m be the index of the last character in the string whose code is greater than '\u0020'. A new String object is created, representing the substring of this string that begins with the character at index k and ends with the character at index m-that is, the result of this.substring(k, m+1).
Let me put that in clearer terms:
If a contiguous block of characters between ASCII 0 and ASCII 32 is found at at the beginning and/or end of the string, the whole block is removed.
ASCII 0 through ASCII 32 means the nul (0) through space (32) characters, inclusive. In that range are the quite common carriage return (13), line break (10), and tab (9) characters, and some more obscure ones like vertical tab (11).[1]
So though it only explicitly mentions the space character \u0020
(hex 20 is decimal 32), which you're probably familiar with as %20
in URLs, in fact it covers line breaks as well. If there's a long intro or outro of spaces, line breaks, and tabs, trim()
will clean 'em all out.
trim()
-ing what's inside a #define
So trim()
is perfect, but you can't simply do this:
<a href="${baseURL.trim()}">${linkText.trim()}</a>
That'll throw an error. The reason is that any #define
, when you address it directly, is a Velocity-specific Block$Reference
, not a generic java.lang.String
.
A Block Reference doesn't have a trim()
method. But it does have a toString()
method. (In fact, toString()
is called under the hood when you output a plain ${reference}
in Velocity, otherwise you couldn't output it at all).
So — I know this was long-winded but hopefully you learned something — you need:
<a href="${baseURL.toString().trim()}">${linkText.toString().trim()}</a>
And you're done!
Notes
[1] But not all whitespace characters, since some as common as non-breaking space (the famous
in HTML) are above ASCII 32. And over in JavaScript, the almost-identically-purposed trim()
does strip non-breaking space. Is there nothing in programming that's not complicated when you care to learn the details? ☺