Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#305: allow ${..} inside ${..} if inside double quotes #306

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions jxls-site/docs/builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ Example:
builder.withExpressionNotation("LBRACELBRACE", "}}")
```

## Expression evaluator context factory

If you need your own ${..} expressions inside the Jxls ${..} expressions you could use ExpressionEvaluatorContextQ.
This class ignores ${..} expressions inside Jxls ${..} expressions that are inside double quotes. Example:
`${services.format("${street} ${houseNo}")}`

`services.format("${street} ${houseNo}")` is the expression that will be given to JEXL.
`${street}` and `${houseNo}` will be evaluated by your code inside services.format()
(where "services" is an object you put to the Jxls data map).

```
builder.withExpressionEvaluatorContextFactory((f, b, e) -> new ExpressionEvaluatorContextQ(f, b, e))
```

## Logging

The `JxlsLogger` interface is the middleman between Jxls and a logger.
Expand Down
10 changes: 9 additions & 1 deletion jxls/src/main/java/org/jxls/builder/JxlsOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
import org.jxls.expression.ExpressionEvaluatorFactory;
import org.jxls.formula.FormulaProcessor;
import org.jxls.logging.JxlsLogger;
import org.jxls.transform.ExpressionEvaluatorContextFactory;
import org.jxls.transform.JxlsTransformerFactory;
import org.jxls.transform.PreWriteAction;

/**
* Internal transport object for delivering the builder options to the template filler
*/
public class JxlsOptions {
private final ExpressionEvaluatorContextFactory expressionEvaluatorContextFactory;
private final ExpressionEvaluatorFactory expressionEvaluatorFactory;
private final String expressionNotationBegin;
private final String expressionNotationEnd;
Expand All @@ -36,13 +38,15 @@ public class JxlsOptions {
private final List<PreWriteAction> preWriteActions;
private final RunVarAccess runVarAccess;

public JxlsOptions(ExpressionEvaluatorFactory expressionEvaluatorFactory, String expressionNotationBegin,
public JxlsOptions(ExpressionEvaluatorContextFactory expressionEvaluatorContextFactory,
ExpressionEvaluatorFactory expressionEvaluatorFactory, String expressionNotationBegin,
String expressionNotationEnd, JxlsLogger logger, FormulaProcessor formulaProcessor, boolean updateCellDataArea,
boolean ignoreColumnProps, boolean ignoreRowProps, boolean recalculateFormulasBeforeSaving,
boolean recalculateFormulasOnOpening, KeepTemplateSheet keepTemplateSheet, AreaBuilder areaBuilder,
Map<String, Class<? extends Command>> commands, boolean clearTemplateCells,
JxlsTransformerFactory transformerFactory, JxlsStreaming streaming, List<NeedsPublicContext> needsContextList,
List<PreWriteAction> preWriteActions, RunVarAccess runVarAccess) {
this.expressionEvaluatorContextFactory = expressionEvaluatorContextFactory;
this.expressionEvaluatorFactory = expressionEvaluatorFactory;
this.expressionNotationBegin = expressionNotationBegin;
this.expressionNotationEnd = expressionNotationEnd;
Expand All @@ -64,6 +68,10 @@ public JxlsOptions(ExpressionEvaluatorFactory expressionEvaluatorFactory, String
this.runVarAccess = runVarAccess;
}

public ExpressionEvaluatorContextFactory getExpressionEvaluatorContextFactory() {
return expressionEvaluatorContextFactory;
}

public ExpressionEvaluatorFactory getExpressionEvaluatorFactory() {
return expressionEvaluatorFactory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ protected Context createContext(ExpressionEvaluatorContext expressionEvaluatorCo
}

protected ExpressionEvaluatorContext createExpressionEvaluatorContext() {
return new ExpressionEvaluatorContext(
return options.getExpressionEvaluatorContextFactory().build(
options.getExpressionEvaluatorFactory(),
options.getExpressionNotationBegin(),
options.getExpressionNotationEnd());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.jxls.formula.FormulaProcessor;
import org.jxls.formula.StandardFormulaProcessor;
import org.jxls.logging.JxlsLogger;
import org.jxls.transform.ExpressionEvaluatorContext;
import org.jxls.transform.ExpressionEvaluatorContextFactory;
import org.jxls.transform.JxlsTransformerFactory;
import org.jxls.transform.PreWriteAction;
import org.jxls.util.CannotOpenWorkbookException;
Expand All @@ -34,6 +36,7 @@
* You must call withTransformerFactory() and withTemplate().
*/
public class JxlsTemplateFillerBuilder<SELF extends JxlsTemplateFillerBuilder<SELF>> {
protected ExpressionEvaluatorContextFactory expressionEvaluatorContextFactory = (f, b, e) -> new ExpressionEvaluatorContext(f, b, e);
private ExpressionEvaluatorFactory expressionEvaluatorFactory = new ExpressionEvaluatorFactoryJexlImpl();
public static final String DEFAULT_EXPRESSION_BEGIN = "${";
public static final String DEFAULT_EXPRESSION_END = "}";
Expand Down Expand Up @@ -117,12 +120,27 @@ public byte[] buildAndFill(Map<String, Object> data) {
* @return JxlsOptions
*/
public JxlsOptions getOptions() {
return new JxlsOptions(expressionEvaluatorFactory, expressionNotationBegin, expressionNotationEnd,
return new JxlsOptions(expressionEvaluatorContextFactory, expressionEvaluatorFactory, expressionNotationBegin, expressionNotationEnd,
logger, formulaProcessor, updateCellDataArea, ignoreColumnProps, ignoreRowProps,
recalculateFormulasBeforeSaving, recalculateFormulasOnOpening, keepTemplateSheet,
areaBuilder, commands, clearTemplateCells, transformerFactory, streaming, needsContextList,
preWriteActions, runVarAccess);
}

/**
* Use this method for defining your own factory for building the ExpressionEvaluatorContext instance.
* This is typically used for exchanging the ExpressionEvaluatorContext.evaluateRawExpression() method.
*
* @param expressionEvaluatorContextFactory not null
* @return this
*/
public SELF withExpressionEvaluatorContextFactory(ExpressionEvaluatorContextFactory expressionEvaluatorContextFactory) {
if (expressionEvaluatorContextFactory == null) {
throw new IllegalArgumentException("expressionEvaluatorContextFactory must not be null");
}
this.expressionEvaluatorContextFactory = expressionEvaluatorContextFactory;
return (SELF) this;
}

/**
* Defines a factory class with which ExpressionEvaluator classes are created during report creation. It is recommended to use
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
public class ExpressionEvaluatorContext {
private static final String EXPRESSION_PART = "(.+?)";

private final ExpressionEvaluatorFactory expressionEvaluatorFactory;
private final String expressionNotationBegin;
private final String expressionNotationEnd;
protected final ExpressionEvaluatorFactory expressionEvaluatorFactory;
protected final String expressionNotationBegin;
protected final String expressionNotationEnd;
private final Pattern expressionNotationPattern;
private ExpressionEvaluator expressionEvaluator = null;
protected ExpressionEvaluator expressionEvaluator = null;

/**
* @param expressionEvaluatorFactory often new ExpressionEvaluatorFactoryJexlImpl()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.jxls.transform;

import org.jxls.expression.ExpressionEvaluatorFactory;

public interface ExpressionEvaluatorContextFactory {

ExpressionEvaluatorContext build(ExpressionEvaluatorFactory expressionEvaluatorFactory, String expressionNotationBegin, String expressionNotationEnd);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.jxls.transform;

import java.util.Map;

import org.jxls.common.EvaluationResult;
import org.jxls.expression.ExpressionEvaluator;
import org.jxls.expression.ExpressionEvaluatorFactory;

/**
* ExpressionEvaluatorContext implementation that allows ${..} expressions inside ${..} if they are contained within double quotes.
*/
public class ExpressionEvaluatorContextQ extends ExpressionEvaluatorContext {

public ExpressionEvaluatorContextQ(ExpressionEvaluatorFactory expressionEvaluatorFactory, String expressionNotationBegin, String expressionNotationEnd) {
super(expressionEvaluatorFactory, expressionNotationBegin, expressionNotationEnd);
}

@Override
public EvaluationResult evaluateRawExpression(String rawExpression, Map<String, Object> data) {
final boolean one = rawExpression.startsWith(expressionNotationBegin) && rawExpression.endsWith(expressionNotationEnd);
final ExpressionEvaluator evaluator = getExpressionEvaluator();
int replacements = 0;
Object lastMatchEvalResult = null;
int o = rawExpression.indexOf(expressionNotationBegin);
while (o >= 0) {
int nextStart = o + expressionNotationBegin.length();
int oo = findEnd(rawExpression, nextStart);
if (oo >= 0) {
String expr = rawExpression.substring(nextStart, oo);
lastMatchEvalResult = evaluator.evaluate(expr, data);
String newRawExpression = rawExpression.substring(0, o);
if (lastMatchEvalResult != null) {
newRawExpression += lastMatchEvalResult;
}
nextStart = newRawExpression.length();
rawExpression = newRawExpression + rawExpression.substring(oo + expressionNotationEnd.length());
replacements++;
}

o = rawExpression.indexOf(expressionNotationBegin, nextStart);
}
if (one && replacements == 1) {
return new EvaluationResult(lastMatchEvalResult, true);
}
return new EvaluationResult(rawExpression);
}

private int findEnd(String rawExpression, final int start0) {
int start = start0;
int end = -1;
boolean again;
do {
again = false;
end = rawExpression.indexOf(expressionNotationEnd, start);
if (end > -1) {
boolean inQuotes = false;
for (int i = start0; i < end; i++) {
char c = rawExpression.charAt(i);
if (c == '"' && !(i - 1 >= 0 && rawExpression.charAt(i - 1) == '\\')) {
inQuotes = !inQuotes;
}
}
if (inQuotes) {
start++; // expressionNotationEnd inside quotes will be ignored
again = true;
}
}
} while (again);
return end;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package org.jxls.transform;

import java.util.HashMap;
import java.util.Map;

import org.junit.Assert;
import org.junit.Test;
import org.jxls.builder.JxlsTemplateFillerBuilder;
import org.jxls.common.EvaluationResult;
import org.jxls.expression.ExpressionEvaluatorFactoryJexlImpl;

public class ExpressionEvaluatorContextQTest {
// You get full test coverage by replacing ExpressionEvaluatorContext with ExpressionEvaluatorContextQ and running all test cases.

@Test
public void addition() {
Map<String, Object> data = new HashMap<>();
data.put("fourty", Integer.valueOf(40));
String expr = "${1+fourty}-30";
ExpressionEvaluatorContext e = new ExpressionEvaluatorContextQ(new ExpressionEvaluatorFactoryJexlImpl(), null, null);

EvaluationResult r = e.evaluateRawExpression(expr, data);

Assert.assertEquals("41-30", r.getResult());
}

@Test
public void callFunction() {
Map<String, Object> data = new HashMap<>();
data.put("fo", new FuncObject());
String expr = "${1+fo.func(\"fourty\")}-30";
ExpressionEvaluatorContext e = new ExpressionEvaluatorContextQ(new ExpressionEvaluatorFactoryJexlImpl(), null, null);

EvaluationResult r = e.evaluateRawExpression(expr, data);

Assert.assertEquals("41-30", r.getResult());
}

@Test
public void nested() {
Map<String, Object> data = new HashMap<>();
data.put("fo", new FuncObject());
String expr = "${1+fo.func2(\"${fourty}\")}-30";
ExpressionEvaluatorContext e = new ExpressionEvaluatorContextQ(new ExpressionEvaluatorFactoryJexlImpl(), null, null);

EvaluationResult r = e.evaluateRawExpression(expr, data);

Assert.assertEquals("41-30", r.getResult());
}

@Test
public void nested_backslashQuote() {
Map<String, Object> data = new HashMap<>();
data.put("fo", new FuncObject());
String expr = "${fo.func2(\"${\\\"fourty\\\"}\")}";
ExpressionEvaluatorContext e = new ExpressionEvaluatorContextQ(new ExpressionEvaluatorFactoryJexlImpl(), null, null);

EvaluationResult r = e.evaluateRawExpression(expr, data);

Assert.assertEquals(80, r.getResult());
}

@Test
public void two() {
Map<String, Object> data = new HashMap<>();
data.put("fourty", "40");
String expr = "${fourty}${fourty}";
ExpressionEvaluatorContext e = new ExpressionEvaluatorContextQ(new ExpressionEvaluatorFactoryJexlImpl(), null, null);

EvaluationResult r = e.evaluateRawExpression(expr, data);

Assert.assertEquals("4040", r.getResult());
}

/**
* Does the builder return the given ExpressionEvaluatorContextFactory instance?
*/
@Test
public void builder() {
ExpressionEvaluatorContextFactory fac = (f, b, e) -> new ExpressionEvaluatorContextQ(f, b, e);

JxlsTemplateFillerBuilder<?> builder = JxlsTemplateFillerBuilder.newInstance().withExpressionEvaluatorContextFactory(fac);
ExpressionEvaluatorContextFactory factory = builder.getOptions().getExpressionEvaluatorContextFactory();

Assert.assertEquals(ExpressionEvaluatorContextQ.class.getSimpleName(),
factory.build(new ExpressionEvaluatorFactoryJexlImpl(), null, null).getClass().getSimpleName());
}

public static class FuncObject {

public Integer func(String name) {
return "fourty".equals(name) ? Integer.valueOf(40) : Integer.valueOf(0);
}

public Integer func2(String name) {
if ("${\"fourty\"}".equals(name)) {
return Integer.valueOf(80);
} else if ("${fourty}".equals(name)) {
return Integer.valueOf(40);
}
return Integer.valueOf(0);
}
}
}