stringtemplate4 icon indicating copy to clipboard operation
stringtemplate4 copied to clipboard

String Attribute Renderer called for all text - not just attributes

Open davjones9 opened this issue 11 years ago • 13 comments

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();
    }
}

davjones9 avatar Jan 10 '13 11:01 davjones9

Can you clarify the following:

  1. The actual output of this test
  2. The expected output of this test
  3. A situation showing the manner in which you'd like to use an attribute renderer

sharwell avatar Jan 10 '13 15:01 sharwell

  1. Failed test with "The renderer must not get called. Object passed in was 1'
  2. Passed test - no output.
  3. 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. &lt;html&gt;)

davjones9 avatar Jan 10 '13 16:01 davjones9

Hmm...I don't think ST should be calling the renderer for raw text...I'll look into it.

parrt avatar Jan 10 '13 18:01 parrt

Thanks. Any help would be much appreciated,

davjones9 avatar Jan 11 '13 08:01 davjones9

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}.

sharwell avatar Feb 08 '13 14:02 sharwell

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?

ootwch avatar Feb 12 '13 15:02 ootwch

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.

parrt avatar Mar 25 '13 21:03 parrt

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.

christianbryan avatar Jan 15 '14 18:01 christianbryan

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?

ShokuninSan avatar Feb 16 '15 16:02 ShokuninSan

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:

  1. 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();
        }
    
    }
    
  2. 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());
    
  3. Explicitly render the escaped strings via the dictionary:

    exampleTemplate(text) ::= <<
    Normal string: <text>
    Java string literal: "<escape.(text)>"
    >>
    

sharwell avatar Feb 16 '15 17:02 sharwell

@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.

ShokuninSan avatar Feb 23 '15 09:02 ShokuninSan

@sharwell, any updates on this issue?

NickAb avatar Oct 28 '16 11:10 NickAb

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.

bannmann avatar Oct 20 '21 22:10 bannmann