stringtemplate4
stringtemplate4 copied to clipboard
String Attribute Renderer called for all text - not just attributes
In version 3 we could code so that an AttributeRenderer
applied to String.class
was only called for attributes set onto the template. We are trying to create the same behaviour with version 4. However, the AttributeRenderer
seems to be called now for all text in the file.
We are unsure if this is a bug or we are making the wrong assumption
See test below to reproduce:
import static org.junit.Assert.fail;
import java.util.Locale;
import org.junit.Test;
import org.stringtemplate.v4.AttributeRenderer;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroupString;
public class StringTemplateRenderDoesNotGetAppliedToWholeStringTemplateTest {
@Test
public void rendererNotCalledWhenNoAttributesSet() {
STGroupString group = new STGroupString("stringTemplateWithNoAttributes() ::= << 1 >>");
group.registerRenderer(String.class, new AttributeRenderer() {
@Override
public String toString(Object o, String formatString, Locale locale) {
fail("The renderer must not get called. Object passed in was '" + o.toString() + "'");
throw new UnsupportedOperationException();
}
});
ST template = group.getInstanceOf("stringTemplateWithNoAttributes");
template.render();
}
}
Can you clarify the following:
- The actual output of this test
- The expected output of this test
- A situation showing the manner in which you'd like to use an attribute renderer
- Failed test with "The renderer must not get called. Object passed in was 1'
- Passed test - no output.
- The only strings to be passed to it are attributes i.e. $attributeName$ (assuming $ is the delimiter) and not in this case 1. I only want to preform specific rendering to attributes. In our case we are Html escaping attributes. However, with this code it html encodes the html (e.g. <html>)
Hmm...I don't think ST should be calling the renderer for raw text...I'll look into it.
Thanks. Any help would be much appreciated,
I think it will be relatively straightforward to address this in some cases, but what do you expect with something like this?
a() ::= <<
<b("literal")>
<b({literal})>
>>
b(text) ::= <<
<text>
>>
For this case, the attribute renderer would be called for the first string "literal"
, but would not be called the second time for the text in the anonymous template {literal}
.
Hmmm... I just have the same problem with escaping .xml...
Basically I would like to get everything escaped which does not originate in the template... But depending on the implementation that might be hard to do?
I wonder if the renderers could check the type of the incoming object. there might be an internal ST object wrapping the "literal" that you could check. Or, you can wrap external objects automatically with a special object that says "is okay to escape me" or something.
Same problem with using StringTemplate V4 with outputting a simple CSV record. Was using a custom String renderer to double quote fields with commas and double quote existing double quotes but found the commas from the template also been passed into the renderer. Resorted to StringTemplate V3 which works fine.
Are there any news on this? Does ST 4.0.8 (foreseen milestone for this) have now a solution for that? (e.g. checking the type of incoming objects in renderers as @parrt already mentioned?
There are so many edge cases in the semantics of this issue, which makes addressing it very difficult. In another project, rather than register a renderer for strings I used the following:
-
Define a class which transforms a
String
in the desired manner. For example, I wanted to output a string using Java's rules for escaped string literals.public class JavaEscapeStringMap extends AbstractMap<String, Object> { @Override public Object get(Object key) { if (key instanceof String) { String str = (String)key; StringBuilder builder = new StringBuilder(str.length()); for (int i = 0; i < str.length(); i++) { switch (str.charAt(i)) { case '\\': builder.append("\\\\"); break; case '\r': // normalize \r\n to just \n break; case '\n': builder.append("\\n"); break; case '"': builder.append("\\\""); break; default: builder.append(str.charAt(i)); break; } } return builder.toString(); } return super.get(key); } @Override public boolean containsKey(Object key) { return key instanceof String; } @Override public Set<Entry<String, Object>> entrySet() { return Collections.emptySet(); } }
-
Explicitly register an instance of the above class as a dictionary in your template group:
STGroup targetGroup = new STGroupFile("MyTemplates.stg"); targetGroup.defineDictionary("escape", new JavaEscapeStringMap());
-
Explicitly render the escaped strings via the dictionary:
exampleTemplate(text) ::= << Normal string: <text> Java string literal: "<escape.(text)>" >>
@sharwell thanks for your feedback. In my particular case it is about a Scala project so I resorted to parser combinators which is the more idiomatic approach anyway.
@sharwell, any updates on this issue?
I stumbled across this problem as well and created #279 in May 2021 (without finding this issue, though). Unfortunately, @parrt has indicated that in the foreseeable future, this problem won't be addressed in StringTemplate due to time constraints.
So as a solution, one could fork StringTemplate and merge the aforementioned PR. Alternatively, one could try out my StringTemplate fork called PureTemplate. It includes this fix and modernizes the Java API while keeping the template language itself the same.