| /* |
| * Copyright (C) 2010 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.clearsilver.jsilver.interpreter; |
| |
| import com.google.clearsilver.jsilver.autoescape.EscapeMode; |
| import com.google.clearsilver.jsilver.data.Data; |
| import com.google.clearsilver.jsilver.data.DataContext; |
| import com.google.clearsilver.jsilver.exceptions.ExceptionUtil; |
| import com.google.clearsilver.jsilver.exceptions.JSilverIOException; |
| import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException; |
| import com.google.clearsilver.jsilver.functions.FunctionExecutor; |
| import com.google.clearsilver.jsilver.syntax.analysis.DepthFirstAdapter; |
| import com.google.clearsilver.jsilver.syntax.node.AAltCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AAutoescapeCommand; |
| import com.google.clearsilver.jsilver.syntax.node.ACallCommand; |
| import com.google.clearsilver.jsilver.syntax.node.ADataCommand; |
| import com.google.clearsilver.jsilver.syntax.node.ADefCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AEachCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AEscapeCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AEvarCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AHardIncludeCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AHardLincludeCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AIfCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AIncludeCommand; |
| import com.google.clearsilver.jsilver.syntax.node.ALincludeCommand; |
| import com.google.clearsilver.jsilver.syntax.node.ALoopCommand; |
| import com.google.clearsilver.jsilver.syntax.node.ALoopIncCommand; |
| import com.google.clearsilver.jsilver.syntax.node.ALoopToCommand; |
| import com.google.clearsilver.jsilver.syntax.node.ALvarCommand; |
| import com.google.clearsilver.jsilver.syntax.node.ANameCommand; |
| import com.google.clearsilver.jsilver.syntax.node.ANameVariable; |
| import com.google.clearsilver.jsilver.syntax.node.ASetCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AUvarCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AVarCommand; |
| import com.google.clearsilver.jsilver.syntax.node.AWithCommand; |
| import com.google.clearsilver.jsilver.syntax.node.PCommand; |
| import com.google.clearsilver.jsilver.syntax.node.PExpression; |
| import com.google.clearsilver.jsilver.syntax.node.PPosition; |
| import com.google.clearsilver.jsilver.syntax.node.PVariable; |
| import com.google.clearsilver.jsilver.syntax.node.TCsOpen; |
| import com.google.clearsilver.jsilver.syntax.node.TWord; |
| import com.google.clearsilver.jsilver.template.Macro; |
| import com.google.clearsilver.jsilver.template.RenderingContext; |
| import com.google.clearsilver.jsilver.template.Template; |
| import com.google.clearsilver.jsilver.template.TemplateLoader; |
| import com.google.clearsilver.jsilver.values.Value; |
| import com.google.clearsilver.jsilver.values.VariableValue; |
| |
| import java.io.IOException; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| |
| /** |
| * Main JSilver interpreter. This walks a template's AST and renders the result out. |
| */ |
| public class TemplateInterpreter extends DepthFirstAdapter { |
| |
| private final Template template; |
| |
| private final ExpressionEvaluator expressionEvaluator; |
| private final VariableLocator variableLocator; |
| private final TemplateLoader templateLoader; |
| private final RenderingContext context; |
| private final DataContext dataContext; |
| |
| public TemplateInterpreter(Template template, TemplateLoader templateLoader, |
| RenderingContext context, FunctionExecutor functionExecutor) { |
| this.template = template; |
| this.templateLoader = templateLoader; |
| this.context = context; |
| this.dataContext = context.getDataContext(); |
| |
| expressionEvaluator = new ExpressionEvaluator(dataContext, functionExecutor); |
| variableLocator = new VariableLocator(expressionEvaluator); |
| } |
| |
| // ------------------------------------------------------------------------ |
| // COMMAND PROCESSING |
| |
| /** |
| * Chunk of data (i.e. not a CS command). |
| */ |
| @Override |
| public void caseADataCommand(ADataCommand node) { |
| context.writeUnescaped(node.getData().getText()); |
| } |
| |
| /** |
| * <?cs var:blah > expression. Evaluate as string and write output, using default escaping. |
| */ |
| @Override |
| public void caseAVarCommand(AVarCommand node) { |
| setLastPosition(node.getPosition()); |
| |
| // Evaluate expression. |
| Value value = expressionEvaluator.evaluate(node.getExpression()); |
| writeVariable(value); |
| } |
| |
| /** |
| * <?cs uvar:blah > expression. Evaluate as string and write output, but don't escape. |
| */ |
| @Override |
| public void caseAUvarCommand(AUvarCommand node) { |
| setLastPosition(node.getPosition()); |
| |
| // Evaluate expression. |
| Value value = expressionEvaluator.evaluate(node.getExpression()); |
| context.writeUnescaped(value.asString()); |
| } |
| |
| /** |
| * <?cs lvar:blah > command. Evaluate expression and execute commands within. |
| */ |
| @Override |
| public void caseALvarCommand(ALvarCommand node) { |
| setLastPosition(node.getPosition()); |
| evaluateVariable(node.getExpression(), "[lvar expression]"); |
| } |
| |
| /** |
| * <?cs evar:blah > command. Evaluate expression and execute commands within. |
| */ |
| @Override |
| public void caseAEvarCommand(AEvarCommand node) { |
| setLastPosition(node.getPosition()); |
| evaluateVariable(node.getExpression(), "[evar expression]"); |
| } |
| |
| private void evaluateVariable(PExpression expression, String stackTraceDescription) { |
| // Evaluate expression. |
| Value value = expressionEvaluator.evaluate(expression); |
| |
| // Now parse result, into new mini template. |
| Template template = |
| templateLoader.createTemp(stackTraceDescription, value.asString(), context |
| .getAutoEscapeMode()); |
| |
| // Intepret new template. |
| try { |
| template.render(context); |
| } catch (IOException e) { |
| throw new JSilverInterpreterException(e.getMessage()); |
| } |
| } |
| |
| /** |
| * <?cs linclude!'somefile.cs' > command. Lazily includes another template (at render time). |
| * Throw an error if file does not exist. |
| */ |
| @Override |
| public void caseAHardLincludeCommand(AHardLincludeCommand node) { |
| setLastPosition(node.getPosition()); |
| include(node.getExpression(), false); |
| } |
| |
| /** |
| * <?cs linclude:'somefile.cs' > command. Lazily includes another template (at render time). |
| * Silently ignore if the included file does not exist. |
| */ |
| @Override |
| public void caseALincludeCommand(ALincludeCommand node) { |
| setLastPosition(node.getPosition()); |
| include(node.getExpression(), true); |
| } |
| |
| /** |
| * <?cs include!'somefile.cs' > command. Throw an error if file does not exist. |
| */ |
| @Override |
| public void caseAHardIncludeCommand(AHardIncludeCommand node) { |
| setLastPosition(node.getPosition()); |
| include(node.getExpression(), false); |
| } |
| |
| /** |
| * <?cs include:'somefile.cs' > command. Silently ignore if the included file does not |
| * exist. |
| */ |
| @Override |
| public void caseAIncludeCommand(AIncludeCommand node) { |
| setLastPosition(node.getPosition()); |
| include(node.getExpression(), true); |
| } |
| |
| /** |
| * <?cs set:x='y' > command. |
| */ |
| @Override |
| public void caseASetCommand(ASetCommand node) { |
| setLastPosition(node.getPosition()); |
| String variableName = variableLocator.getVariableName(node.getVariable()); |
| |
| try { |
| Data variable = dataContext.findVariable(variableName, true); |
| Value value = expressionEvaluator.evaluate(node.getExpression()); |
| variable.setValue(value.asString()); |
| // TODO: what about nested structures? |
| // "set" was used to set a variable to a constant or escaped value like |
| // <?cs set: x = "<b>X</b>" ?> or <?cs set: y = html_escape(x) ?> |
| // Keep track of this so autoescaping code can take it into account. |
| variable.setEscapeMode(value.getEscapeMode()); |
| } catch (UnsupportedOperationException e) { |
| // An error occurred - probably due to trying to modify an UnmodifiableData |
| throw new UnsupportedOperationException(createUnsupportedOperationMessage(node, context |
| .getIncludedTemplateNames()), e); |
| } |
| } |
| |
| /** |
| * <?cs name:blah > command. Writes out the name of the original variable referred to by a |
| * given node. |
| */ |
| @Override |
| public void caseANameCommand(ANameCommand node) { |
| setLastPosition(node.getPosition()); |
| String variableName = variableLocator.getVariableName(node.getVariable()); |
| Data variable = dataContext.findVariable(variableName, false); |
| if (variable != null) { |
| context.writeEscaped(variable.getSymlink().getName()); |
| } |
| } |
| |
| /** |
| * <?cs if:blah > ... <?cs else > ... <?cs /if > command. |
| */ |
| @Override |
| public void caseAIfCommand(AIfCommand node) { |
| setLastPosition(node.getPosition()); |
| Value value = expressionEvaluator.evaluate(node.getExpression()); |
| if (value.asBoolean()) { |
| node.getBlock().apply(this); |
| } else { |
| node.getOtherwise().apply(this); |
| } |
| } |
| |
| |
| /** |
| * <?cs escape:'html' > command. Changes default escaping function. |
| */ |
| @Override |
| public void caseAEscapeCommand(AEscapeCommand node) { |
| setLastPosition(node.getPosition()); |
| Value value = expressionEvaluator.evaluate(node.getExpression()); |
| String escapeStrategy = value.asString(); |
| |
| context.pushEscapingFunction(escapeStrategy); |
| node.getCommand().apply(this); |
| context.popEscapingFunction(); |
| } |
| |
| /** |
| * A fake command injected by AutoEscaper. |
| * |
| * AutoEscaper determines the html context in which an include or lvar or evar command is called |
| * and stores this context in the AAutoescapeCommand node. |
| */ |
| @Override |
| public void caseAAutoescapeCommand(AAutoescapeCommand node) { |
| setLastPosition(node.getPosition()); |
| Value value = expressionEvaluator.evaluate(node.getExpression()); |
| String escapeStrategy = value.asString(); |
| |
| EscapeMode mode = EscapeMode.computeEscapeMode(escapeStrategy); |
| |
| context.pushAutoEscapeMode(mode); |
| node.getCommand().apply(this); |
| context.popAutoEscapeMode(); |
| } |
| |
| /** |
| * <?cs with:x=Something > ... <?cs /with > command. Aliases a value within a specific |
| * scope. |
| */ |
| @Override |
| public void caseAWithCommand(AWithCommand node) { |
| setLastPosition(node.getPosition()); |
| VariableLocator variableLocator = new VariableLocator(expressionEvaluator); |
| String withVar = variableLocator.getVariableName(node.getVariable()); |
| Value value = expressionEvaluator.evaluate(node.getExpression()); |
| |
| if (value instanceof VariableValue) { |
| if (((VariableValue) value).getReference() == null) { |
| // With refers to a non-existent variable. Do nothing. |
| return; |
| } |
| } |
| |
| dataContext.pushVariableScope(); |
| setTempVariable(withVar, value); |
| node.getCommand().apply(this); |
| dataContext.popVariableScope(); |
| } |
| |
| /** |
| * <?cs loop:10 > ... <?cs /loop > command. Loops over a range of numbers, starting at |
| * zero. |
| */ |
| @Override |
| public void caseALoopToCommand(ALoopToCommand node) { |
| setLastPosition(node.getPosition()); |
| int end = expressionEvaluator.evaluate(node.getExpression()).asNumber(); |
| |
| // Start is always zero, increment is always 1, so end < 0 is invalid. |
| if (end < 0) { |
| return; // Incrementing the wrong way. Avoid infinite loop. |
| } |
| |
| loop(node.getVariable(), 0, end, 1, node.getCommand()); |
| } |
| |
| /** |
| * <?cs loop:0,10 > ... <?cs /loop > command. Loops over a range of numbers. |
| */ |
| @Override |
| public void caseALoopCommand(ALoopCommand node) { |
| setLastPosition(node.getPosition()); |
| int start = expressionEvaluator.evaluate(node.getStart()).asNumber(); |
| int end = expressionEvaluator.evaluate(node.getEnd()).asNumber(); |
| |
| // Start is always zero, increment is always 1, so end < 0 is invalid. |
| if (end < start) { |
| return; // Incrementing the wrong way. Avoid infinite loop. |
| } |
| |
| loop(node.getVariable(), start, end, 1, node.getCommand()); |
| } |
| |
| /** |
| * <?cs loop:0,10,2 > ... <?cs /loop > command. Loops over a range of numbers, with a |
| * specific increment. |
| */ |
| @Override |
| public void caseALoopIncCommand(ALoopIncCommand node) { |
| setLastPosition(node.getPosition()); |
| int start = expressionEvaluator.evaluate(node.getStart()).asNumber(); |
| int end = expressionEvaluator.evaluate(node.getEnd()).asNumber(); |
| int incr = expressionEvaluator.evaluate(node.getIncrement()).asNumber(); |
| |
| if (incr == 0) { |
| return; // No increment. Avoid infinite loop. |
| } |
| if (incr > 0 && start > end) { |
| return; // Incrementing the wrong way. Avoid infinite loop. |
| } |
| if (incr < 0 && start < end) { |
| return; // Incrementing the wrong way. Avoid infinite loop. |
| } |
| |
| loop(node.getVariable(), start, end, incr, node.getCommand()); |
| } |
| |
| /** |
| * <?cs each:x=Stuff > ... <?cs /each > command. Loops over child items of a data |
| * node. |
| */ |
| @Override |
| public void caseAEachCommand(AEachCommand node) { |
| setLastPosition(node.getPosition()); |
| Value expression = expressionEvaluator.evaluate(node.getExpression()); |
| |
| if (expression instanceof VariableValue) { |
| VariableValue variableValue = (VariableValue) expression; |
| Data parent = variableValue.getReference(); |
| if (parent != null) { |
| each(node.getVariable(), variableValue.getName(), parent, node.getCommand()); |
| } |
| } |
| } |
| |
| /** |
| * <?cs alt:someValue > ... <?cs /alt > command. If value exists, write it, otherwise |
| * write the body of the command. |
| */ |
| @Override |
| public void caseAAltCommand(AAltCommand node) { |
| setLastPosition(node.getPosition()); |
| Value value = expressionEvaluator.evaluate(node.getExpression()); |
| if (value.asBoolean()) { |
| writeVariable(value); |
| } else { |
| node.getCommand().apply(this); |
| } |
| } |
| |
| private void writeVariable(Value value) { |
| if (template.getEscapeMode().isAutoEscapingMode()) { |
| autoEscapeAndWriteVariable(value); |
| } else if (value.isPartiallyEscaped()) { |
| context.writeUnescaped(value.asString()); |
| } else { |
| context.writeEscaped(value.asString()); |
| } |
| } |
| |
| private void autoEscapeAndWriteVariable(Value value) { |
| if (isTrustedValue(value) || value.isPartiallyEscaped()) { |
| context.writeUnescaped(value.asString()); |
| } else { |
| context.writeEscaped(value.asString()); |
| } |
| } |
| |
| private boolean isTrustedValue(Value value) { |
| // True if PropagateEscapeStatus is enabled and value has either been |
| // escaped or contains a constant string. |
| return context.getAutoEscapeOptions().getPropagateEscapeStatus() |
| && !value.getEscapeMode().equals(EscapeMode.ESCAPE_NONE); |
| } |
| |
| // ------------------------------------------------------------------------ |
| // MACROS |
| |
| /** |
| * <?cs def:someMacro(x,y) > ... <?cs /def > command. Define a macro (available for |
| * the remainder of the interpreter context. |
| */ |
| @Override |
| public void caseADefCommand(ADefCommand node) { |
| String macroName = makeWord(node.getMacro()); |
| LinkedList<PVariable> arguments = node.getArguments(); |
| String[] argumentNames = new String[arguments.size()]; |
| int i = 0; |
| for (PVariable argument : arguments) { |
| if (!(argument instanceof ANameVariable)) { |
| throw new JSilverInterpreterException("Invalid name for macro '" + macroName |
| + "' argument " + i + " : " + argument); |
| } |
| argumentNames[i++] = ((ANameVariable) argument).getWord().getText(); |
| } |
| // TODO: Should we enforce that macro args can't repeat the same |
| // name? |
| context.registerMacro(macroName, new InterpretedMacro(node.getCommand(), template, macroName, |
| argumentNames, this, context)); |
| } |
| |
| private String makeWord(LinkedList<TWord> words) { |
| if (words.size() == 1) { |
| return words.getFirst().getText(); |
| } |
| StringBuilder result = new StringBuilder(); |
| for (TWord word : words) { |
| if (result.length() > 0) { |
| result.append('.'); |
| } |
| result.append(word.getText()); |
| } |
| return result.toString(); |
| } |
| |
| /** |
| * <?cs call:someMacro(x,y) command. Call a macro. Need to create a new variable scope to hold |
| * the local variables defined by the parameters of the macro definition |
| */ |
| @Override |
| public void caseACallCommand(ACallCommand node) { |
| String macroName = makeWord(node.getMacro()); |
| Macro macro = context.findMacro(macroName); |
| |
| // Make sure that the number of arguments passed to the macro match the |
| // number expected. |
| if (node.getArguments().size() != macro.getArgumentCount()) { |
| throw new JSilverInterpreterException("Number of arguments to macro " + macroName + " (" |
| + node.getArguments().size() + ") does not match " + "number of expected arguments (" |
| + macro.getArgumentCount() + ")"); |
| } |
| |
| int numArgs = node.getArguments().size(); |
| if (numArgs > 0) { |
| Value[] argValues = new Value[numArgs]; |
| |
| // We must first evaluate the parameters we are passing or there could be |
| // conflicts if new argument names match existing variables. |
| Iterator<PExpression> argumentValues = node.getArguments().iterator(); |
| for (int i = 0; argumentValues.hasNext(); i++) { |
| argValues[i] = expressionEvaluator.evaluate(argumentValues.next()); |
| } |
| |
| // No need to bother pushing and popping the variable scope stack |
| // if there are no new local variables to declare. |
| dataContext.pushVariableScope(); |
| |
| for (int i = 0; i < argValues.length; i++) { |
| setTempVariable(macro.getArgumentName(i), argValues[i]); |
| } |
| } |
| try { |
| macro.render(context); |
| } catch (IOException e) { |
| throw new JSilverIOException(e); |
| } |
| if (numArgs > 0) { |
| // No need to bother pushing and popping the variable scope stack |
| // if there are no new local variables to declare. |
| dataContext.popVariableScope(); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // HELPERS |
| // |
| // Much of the functionality in this section could easily be inlined, |
| // however it makes the rest of the interpreter much easier to understand |
| // and refactor with them defined here. |
| |
| private void each(PVariable variable, String parentName, Data items, PCommand command) { |
| // Since HDF variables are now passed to macro parameters by path name |
| // we need to create a path for each child when generating the |
| // VariableValue object. |
| VariableLocator variableLocator = new VariableLocator(expressionEvaluator); |
| String eachVar = variableLocator.getVariableName(variable); |
| StringBuilder pathBuilder = new StringBuilder(parentName); |
| pathBuilder.append('.'); |
| int length = pathBuilder.length(); |
| dataContext.pushVariableScope(); |
| for (Data child : items.getChildren()) { |
| pathBuilder.delete(length, pathBuilder.length()); |
| pathBuilder.append(child.getName()); |
| setTempVariable(eachVar, Value.variableValue(pathBuilder.toString(), dataContext)); |
| command.apply(this); |
| } |
| dataContext.popVariableScope(); |
| } |
| |
| private void loop(PVariable loopVar, int start, int end, int incr, PCommand command) { |
| VariableLocator variableLocator = new VariableLocator(expressionEvaluator); |
| String varName = variableLocator.getVariableName(loopVar); |
| |
| dataContext.pushVariableScope(); |
| // Loop deals with counting forward or backwards. |
| for (int index = start; incr > 0 ? index <= end : index >= end; index += incr) { |
| // We reuse the same scope for efficiency and simply overwrite the |
| // previous value of the loop variable. |
| dataContext.createLocalVariableByValue(varName, String.valueOf(index), index == start, |
| index == end); |
| |
| command.apply(this); |
| } |
| dataContext.popVariableScope(); |
| } |
| |
| /** |
| * Code common to all three include commands. |
| * |
| * @param expression expression representing name of file to include. |
| * @param ignoreMissingFile {@code true} if any FileNotFound error generated by the template |
| * loader should be ignored, {@code false} otherwise. |
| */ |
| private void include(PExpression expression, boolean ignoreMissingFile) { |
| // Evaluate expression. |
| Value path = expressionEvaluator.evaluate(expression); |
| |
| String templateName = path.asString(); |
| if (!context.pushIncludeStackEntry(templateName)) { |
| throw new JSilverInterpreterException(createIncludeLoopErrorMessage(templateName, context |
| .getIncludedTemplateNames())); |
| } |
| |
| loadAndRenderIncludedTemplate(templateName, ignoreMissingFile); |
| |
| if (!context.popIncludeStackEntry(templateName)) { |
| // Include stack trace is corrupted |
| throw new IllegalStateException("Unable to find on include stack: " + templateName); |
| } |
| } |
| |
| private String createIncludeLoopErrorMessage(String templateName, Iterable<String> includeStack) { |
| StringBuilder message = new StringBuilder(); |
| message.append("File included twice: "); |
| message.append(templateName); |
| |
| message.append(" Include stack:"); |
| for (String fileName : includeStack) { |
| message.append("\n -> "); |
| message.append(fileName); |
| } |
| message.append("\n -> "); |
| message.append(templateName); |
| return message.toString(); |
| } |
| |
| private String createUnsupportedOperationMessage(PCommand node, Iterable<String> includeStack) { |
| StringBuilder message = new StringBuilder(); |
| |
| message.append("exception thrown while parsing node: "); |
| message.append(node.toString()); |
| message.append(" (class ").append(node.getClass().getSimpleName()).append(")"); |
| message.append("\nTemplate include stack: "); |
| |
| for (Iterator<String> iter = includeStack.iterator(); iter.hasNext();) { |
| message.append(iter.next()); |
| if (iter.hasNext()) { |
| message.append(" -> "); |
| } |
| } |
| message.append("\n"); |
| |
| return message.toString(); |
| } |
| |
| // This method should ONLY be called from include() |
| private void loadAndRenderIncludedTemplate(String templateName, boolean ignoreMissingFile) { |
| // Now load new template with given name. |
| Template template = null; |
| try { |
| template = |
| templateLoader.load(templateName, context.getResourceLoader(), context |
| .getAutoEscapeMode()); |
| } catch (RuntimeException e) { |
| if (ignoreMissingFile && ExceptionUtil.isFileNotFoundException(e)) { |
| return; |
| } else { |
| throw e; |
| } |
| } |
| |
| // Intepret loaded template. |
| try { |
| // TODO: Execute lincludes (but not includes) in a separate |
| // context. |
| template.render(context); |
| } catch (IOException e) { |
| throw new JSilverInterpreterException(e.getMessage()); |
| } |
| } |
| |
| private void setLastPosition(PPosition position) { |
| // Walks position node which will eventually result in calling |
| // caseTCsOpen(). |
| position.apply(this); |
| } |
| |
| /** |
| * Every time a <cs token is found, grab the line and position (for helpful error messages). |
| */ |
| @Override |
| public void caseTCsOpen(TCsOpen node) { |
| int line = node.getLine(); |
| int column = node.getPos(); |
| context.setCurrentPosition(line, column); |
| } |
| |
| private void setTempVariable(String variableName, Value value) { |
| if (value instanceof VariableValue) { |
| // If the value is a Data variable name, then we store a reference to its |
| // name as discovered by the expression evaluator and resolve it each |
| // time for correctness. |
| dataContext.createLocalVariableByPath(variableName, ((VariableValue) value).getName()); |
| } else { |
| dataContext.createLocalVariableByValue(variableName, value.asString(), value.getEscapeMode()); |
| } |
| } |
| |
| } |