In Velocity tokens, trim() your #defines

SanfordWhiteman
Level 10 - Community Moderator
Level 10 - Community Moderator

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 itself 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 &nbsp​; 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? ☺

2343
1
1 Comment