View Javadoc

1   package org.apache.velocity.runtime.parser.node;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements. See the NOTICE file distributed with this
6    * work for additional information regarding copyright ownership. The ASF
7    * licenses this file to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   * http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16   * License for the specific language governing permissions and limitations under
17   * the License.
18   */
19  
20  import java.io.IOException;
21  import java.io.StringReader;
22  import java.io.StringWriter;
23  
24  import org.apache.commons.lang.text.StrBuilder;
25  import org.apache.velocity.context.InternalContextAdapter;
26  import org.apache.velocity.exception.TemplateInitException;
27  import org.apache.velocity.exception.VelocityException;
28  import org.apache.velocity.runtime.RuntimeConstants;
29  import org.apache.velocity.runtime.log.Log;
30  import org.apache.velocity.runtime.parser.ParseException;
31  import org.apache.velocity.runtime.parser.Parser;
32  import org.apache.velocity.runtime.parser.Token;
33  
34  /**
35   * ASTStringLiteral support. Will interpolate!
36   * 
37   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
38   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
39   * @version $Id: ASTStringLiteral.java 1032134 2010-11-06 20:19:39Z cbrisson $
40   */
41  public class ASTStringLiteral extends SimpleNode
42  {
43      /* cache the value of the interpolation switch */
44      private boolean interpolate = true;
45  
46      private SimpleNode nodeTree = null;
47  
48      private String image = "";
49  
50      private String interpolateimage = "";
51  
52      /** true if the string contains a line comment (##) */
53      private boolean containsLineComment;
54  
55      /**
56       * @param id
57       */
58      public ASTStringLiteral(int id)
59      {
60          super(id);
61      }
62  
63      /**
64       * @param p
65       * @param id
66       */
67      public ASTStringLiteral(Parser p, int id)
68      {
69          super(p, id);
70      }
71  
72      /**
73       * init : we don't have to do much. Init the tree (there shouldn't be one)
74       * and then see if interpolation is turned on.
75       * 
76       * @param context
77       * @param data
78       * @return Init result.
79       * @throws TemplateInitException
80       */
81      public Object init(InternalContextAdapter context, Object data)
82              throws TemplateInitException
83      {
84          /*
85           * simple habit... we prollie don't have an AST beneath us
86           */
87  
88          super.init(context, data);
89  
90          /*
91           * the stringlit is set at template parse time, so we can do this here
92           * for now. if things change and we can somehow create stringlits at
93           * runtime, this must move to the runtime execution path
94           * 
95           * so, only if interpolation is turned on AND it starts with a " AND it
96           * has a directive or reference, then we can interpolate. Otherwise,
97           * don't bother.
98           */
99  
100         interpolate = rsvc.getBoolean(
101                 RuntimeConstants.INTERPOLATE_STRINGLITERALS, true)
102                 && getFirstToken().image.startsWith("\"")
103                 && ((getFirstToken().image.indexOf('$') != -1) || (getFirstToken().image
104                         .indexOf('#') != -1));
105 
106         /*
107          * get the contents of the string, minus the '/" at each end
108          */
109         String img = getFirstToken().image;
110         
111         image = img.substring(1, img.length() - 1);
112         
113         if (img.startsWith("\""))
114         {
115             image = unescape(image);
116         }
117         if (img.charAt(0) == '"' || img.charAt(0) == '\'' )
118         {
119             // replace double-double quotes like "" with a single double quote "
120             // replace double single quotes '' with a single quote '
121             image = replaceQuotes(image, img.charAt(0));
122         }
123 
124         /**
125          * note. A kludge on a kludge. The first part, Geir calls this the
126          * dreaded <MORE> kludge. Basically, the use of the <MORE> token eats
127          * the last character of an interpolated string. EXCEPT when a line
128          * comment (##) is in the string this isn't an issue.
129          * 
130          * So, to solve this we look for a line comment. If it isn't found we
131          * add a space here and remove it later.
132          */
133 
134         /**
135          * Note - this should really use a regexp to look for [^\]## but
136          * apparently escaping of line comments isn't working right now anyway.
137          */
138         containsLineComment = (image.indexOf("##") != -1);
139 
140         /*
141          * if appropriate, tack a space on the end (dreaded <MORE> kludge)
142          */
143 
144         if (!containsLineComment)
145         {
146             interpolateimage = image + " ";
147         }
148         else
149         {
150             interpolateimage = image;
151         }
152 
153         if (interpolate)
154         {
155             /*
156              * now parse and init the nodeTree
157              */
158             StringReader br = new StringReader(interpolateimage);
159 
160             /*
161              * it's possible to not have an initialization context - or we don't
162              * want to trust the caller - so have a fallback value if so
163              * 
164              * Also, do *not* dump the VM namespace for this template
165              */
166 
167             String templateName =
168                 (context != null) ? context.getCurrentTemplateName() : "StringLiteral";
169             try
170             {
171                 nodeTree = rsvc.parse(br, templateName, false);
172             }
173             catch (ParseException e)
174             {
175                 String msg = "Failed to parse String literal at "+
176                     Log.formatFileString(templateName, getLine(), getColumn());
177                 throw new TemplateInitException(msg, e, templateName, getColumn(), getLine());
178             }
179 
180             adjTokenLineNums(nodeTree);
181             
182             /*
183              * init with context. It won't modify anything
184              */
185 
186             nodeTree.init(context, rsvc);
187         }
188 
189         return data;
190     }
191     
192     /**
193      * Adjust all the line and column numbers that comprise a node so that they
194      * are corrected for the string literals position within the template file.
195      * This is neccessary if an exception is thrown while processing the node so
196      * that the line and column position reported reflects the error position
197      * within the template and not just relative to the error position within
198      * the string literal.
199      */
200     public void adjTokenLineNums(Node node)
201     {
202         Token tok = node.getFirstToken();
203         // Test against null is probably not neccessary, but just being safe
204         while(tok != null && tok != node.getLastToken())
205         {
206             // If tok is on the first line, then the actual column is 
207             // offset by the template column.
208           
209             if (tok.beginLine == 1)
210                 tok.beginColumn += getColumn();
211             
212             if (tok.endLine == 1)
213                 tok.endColumn += getColumn();
214             
215             tok.beginLine += getLine()- 1;
216             tok.endLine += getLine() - 1;
217             tok = tok.next;
218         }
219     }
220 
221     /**
222      * Replaces double double-quotes with a single double quote ("" to ").
223      * Replaces double single quotes with a single quote ('' to ').
224 	 *
225 	 * @param s StringLiteral without the surrounding quotes
226 	 * @param literalQuoteChar char that starts the StringLiteral (" or ')
227      */     
228     private String replaceQuotes(String s, char literalQuoteChar)
229     {
230         if( (literalQuoteChar == '"' && s.indexOf("\"") == -1) ||
231             (literalQuoteChar == '\'' && s.indexOf("'") == -1) )
232         {
233             return s;
234         }
235     
236         StrBuilder result = new StrBuilder(s.length());
237         char prev = ' ';
238         for(int i = 0, is = s.length(); i < is; i++)
239         {
240             char c = s.charAt(i);
241             result.append(c);
242           
243             if( i + 1 < is )
244             {
245                 char next =  s.charAt(i + 1);
246 				// '""' -> "", "''" -> '' 
247 				// thus it is not necessary to double quotes if the "surrounding" quotes
248 				// of the StringLiteral are different. See VELOCITY-785
249                 if( (literalQuoteChar == '"' && (next == '"' && c == '"')) || 
250 				    (literalQuoteChar == '\'' && (next == '\'' && c == '\'')) )
251                 {
252                     i++;
253                 }
254            }    
255         }
256         return result.toString();
257     }
258     
259     /**
260      * @since 1.6
261      */
262     public static String unescape(final String string)
263     {
264         int u = string.indexOf("\\u");
265         if (u < 0) return string;
266 
267         StrBuilder result = new StrBuilder();
268         
269         int lastCopied = 0;
270 
271         for (;;)
272         {
273             result.append(string.substring(lastCopied, u));
274 
275             /* we don't worry about an exception here,
276              * because the lexer checked that string is correct */
277             char c = (char) Integer.parseInt(string.substring(u + 2, u + 6), 16);
278             result.append(c);
279 
280             lastCopied = u + 6;
281 
282             u = string.indexOf("\\u", lastCopied);
283             if (u < 0)
284             {
285                 result.append(string.substring(lastCopied));
286                 return result.toString();
287             }
288         }
289     }
290 
291 
292     /**
293      * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor,
294      *      java.lang.Object)
295      */
296     public Object jjtAccept(ParserVisitor visitor, Object data)
297     {
298         return visitor.visit(this, data);
299     }
300 
301     /**
302      * Check to see if this is an interpolated string.
303      * @return true if this is constant (not an interpolated string)
304      * @since 1.6
305      */
306     public boolean isConstant()
307     {
308         return !interpolate;
309     }
310 
311     /**
312      * renders the value of the string literal If the properties allow, and the
313      * string literal contains a $ or a # the literal is rendered against the
314      * context Otherwise, the stringlit is returned.
315      * 
316      * @param context
317      * @return result of the rendering.
318      */
319     public Object value(InternalContextAdapter context)
320     {
321         if (interpolate)
322         {
323             try
324             {
325                 /*
326                  * now render against the real context
327                  */
328 
329                 StringWriter writer = new StringWriter();
330                 nodeTree.render(context, writer);
331 
332                 /*
333                  * and return the result as a String
334                  */
335 
336                 String ret = writer.toString();
337 
338                 /*
339                  * if appropriate, remove the space from the end (dreaded <MORE>
340                  * kludge part deux)
341                  */
342                 if (!containsLineComment && ret.length() > 0)
343                 {
344                     return ret.substring(0, ret.length() - 1);
345                 }
346                 else
347                 {
348                     return ret;
349                 }
350             }
351 
352             /**
353              * pass through application level runtime exceptions
354              */
355             catch (RuntimeException e)
356             {
357                 throw e;
358             }
359 
360             catch (IOException e)
361             {
362                 String msg = "Error in interpolating string literal";
363                 log.error(msg, e);
364                 throw new VelocityException(msg, e);
365             }
366 
367         }
368 
369         /*
370          * ok, either not allowed to interpolate, there wasn't a ref or
371          * directive, or we failed, so just output the literal
372          */
373 
374         return image;
375     }
376 }