|
|
@ -1,39 +1,39 @@
|
|
|
|
package org.hl7.fhir.r5.utils;
|
|
|
|
package org.hl7.fhir.r5.utils;
|
|
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
/*
|
|
|
|
Copyright (c) 2011+, HL7, Inc.
|
|
|
|
Copyright (c) 2011+, HL7, Inc.
|
|
|
|
All rights reserved.
|
|
|
|
All rights reserved.
|
|
|
|
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
|
|
are permitted provided that the following conditions are met:
|
|
|
|
are permitted provided that the following conditions are met:
|
|
|
|
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright notice, this
|
|
|
|
* Redistributions of source code must retain the above copyright notice, this
|
|
|
|
list of conditions and the following disclaimer.
|
|
|
|
list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
this list of conditions and the following disclaimer in the documentation
|
|
|
|
this list of conditions and the following disclaimer in the documentation
|
|
|
|
and/or other materials provided with the distribution.
|
|
|
|
and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of HL7 nor the names of its contributors may be used to
|
|
|
|
* Neither the name of HL7 nor the names of its contributors may be used to
|
|
|
|
endorse or promote products derived from this software without specific
|
|
|
|
endorse or promote products derived from this software without specific
|
|
|
|
prior written permission.
|
|
|
|
prior written permission.
|
|
|
|
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
|
|
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
|
|
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
|
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
|
|
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import org.hl7.fhir.exceptions.FHIRException;
|
|
|
|
import org.hl7.fhir.exceptions.FHIRException;
|
|
|
|
import org.hl7.fhir.exceptions.PathEngineException;
|
|
|
|
import org.hl7.fhir.exceptions.PathEngineException;
|
|
|
@ -55,10 +55,10 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
public interface ILiquidEngineIcludeResolver {
|
|
|
|
public interface ILiquidEngineIcludeResolver {
|
|
|
|
public String fetchInclude(LiquidEngine engine, String name);
|
|
|
|
public String fetchInclude(LiquidEngine engine, String name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private IEvaluationContext externalHostServices;
|
|
|
|
private IEvaluationContext externalHostServices;
|
|
|
|
private FHIRPathEngine engine;
|
|
|
|
private FHIRPathEngine engine;
|
|
|
|
private ILiquidEngineIcludeResolver includeResolver;
|
|
|
|
private ILiquidEngineIcludeResolver includeResolver;
|
|
|
|
|
|
|
|
|
|
|
|
private class LiquidEngineContext {
|
|
|
|
private class LiquidEngineContext {
|
|
|
|
private Object externalContext;
|
|
|
|
private Object externalContext;
|
|
|
@ -82,7 +82,7 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
engine = new FHIRPathEngine(context);
|
|
|
|
engine = new FHIRPathEngine(context);
|
|
|
|
engine.setHostServices(this);
|
|
|
|
engine.setHostServices(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ILiquidEngineIcludeResolver getIncludeResolver() {
|
|
|
|
public ILiquidEngineIcludeResolver getIncludeResolver() {
|
|
|
|
return includeResolver;
|
|
|
|
return includeResolver;
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -95,7 +95,7 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
return new LiquidParser(source).parse(sourceName);
|
|
|
|
return new LiquidParser(source).parse(sourceName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public String evaluate(LiquidDocument document, Resource resource, Object appContext) throws FHIRException {
|
|
|
|
public String evaluate(LiquidDocument document, Base resource, Object appContext) throws FHIRException {
|
|
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
LiquidEngineContext ctxt = new LiquidEngineContext(appContext);
|
|
|
|
LiquidEngineContext ctxt = new LiquidEngineContext(appContext);
|
|
|
|
for (LiquidNode n : document.body) {
|
|
|
|
for (LiquidNode n : document.body) {
|
|
|
@ -105,9 +105,10 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private abstract class LiquidNode {
|
|
|
|
private abstract class LiquidNode {
|
|
|
|
protected void closeUp() {}
|
|
|
|
protected void closeUp() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public abstract void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) throws FHIRException;
|
|
|
|
public abstract void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class LiquidConstant extends LiquidNode {
|
|
|
|
private class LiquidConstant extends LiquidNode {
|
|
|
@ -125,7 +126,7 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) {
|
|
|
|
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) {
|
|
|
|
b.append(constant);
|
|
|
|
b.append(constant);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -135,48 +136,191 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
private ExpressionNode compiled;
|
|
|
|
private ExpressionNode compiled;
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
if (compiled == null)
|
|
|
|
if (compiled == null)
|
|
|
|
compiled = engine.parse(statement);
|
|
|
|
compiled = engine.parse(statement);
|
|
|
|
b.append(engine.evaluateToString(ctxt, resource, resource, resource, compiled));
|
|
|
|
b.append(engine.evaluateToString(ctxt, resource, resource, resource, compiled));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class LiquidElsIf extends LiquidNode {
|
|
|
|
|
|
|
|
private String condition;
|
|
|
|
|
|
|
|
private ExpressionNode compiled;
|
|
|
|
|
|
|
|
private List<LiquidNode> body = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
|
|
|
|
for (LiquidNode n : body) {
|
|
|
|
|
|
|
|
n.evaluate(b, resource, ctxt);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class LiquidIf extends LiquidNode {
|
|
|
|
private class LiquidIf extends LiquidNode {
|
|
|
|
private String condition;
|
|
|
|
private String condition;
|
|
|
|
private ExpressionNode compiled;
|
|
|
|
private ExpressionNode compiled;
|
|
|
|
private List<LiquidNode> thenBody = new ArrayList<>();
|
|
|
|
private List<LiquidNode> thenBody = new ArrayList<>();
|
|
|
|
|
|
|
|
private List<LiquidElsIf> elseIf = new ArrayList<>();
|
|
|
|
private List<LiquidNode> elseBody = new ArrayList<>();
|
|
|
|
private List<LiquidNode> elseBody = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
if (compiled == null)
|
|
|
|
if (compiled == null)
|
|
|
|
compiled = engine.parse(condition);
|
|
|
|
compiled = engine.parse(condition);
|
|
|
|
boolean ok = engine.evaluateToBoolean(ctxt, resource, resource, resource, compiled);
|
|
|
|
boolean ok = engine.evaluateToBoolean(ctxt, resource, resource, resource, compiled);
|
|
|
|
List<LiquidNode> list = ok ? thenBody : elseBody;
|
|
|
|
List<LiquidNode> list = null;
|
|
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
|
|
list = thenBody;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
list = elseBody;
|
|
|
|
|
|
|
|
for (LiquidElsIf i : elseIf) {
|
|
|
|
|
|
|
|
if (i.compiled == null)
|
|
|
|
|
|
|
|
i.compiled = engine.parse(i.condition);
|
|
|
|
|
|
|
|
ok = engine.evaluateToBoolean(ctxt, resource, resource, resource, i.compiled);
|
|
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
|
|
|
list = i.body;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
for (LiquidNode n : list) {
|
|
|
|
for (LiquidNode n : list) {
|
|
|
|
n.evaluate(b, resource, ctxt);
|
|
|
|
n.evaluate(b, resource, ctxt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class LiquidLoop extends LiquidNode {
|
|
|
|
private class LiquidContinueExecuted extends FHIRException {
|
|
|
|
|
|
|
|
private static final long serialVersionUID = 4748737094188943721L;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class LiquidContinue extends LiquidNode {
|
|
|
|
|
|
|
|
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
|
|
|
|
throw new LiquidContinueExecuted();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class LiquidBreakExecuted extends FHIRException {
|
|
|
|
|
|
|
|
private static final long serialVersionUID = 6328496371172871082L;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class LiquidBreak extends LiquidNode {
|
|
|
|
|
|
|
|
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
|
|
|
|
throw new LiquidBreakExecuted();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class LiquidCycle extends LiquidNode {
|
|
|
|
|
|
|
|
private List<String> list = new ArrayList<>();
|
|
|
|
|
|
|
|
private int cursor = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
|
|
|
|
b.append(list.get(cursor));
|
|
|
|
|
|
|
|
cursor++;
|
|
|
|
|
|
|
|
if (cursor == list.size()) {
|
|
|
|
|
|
|
|
cursor = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private class LiquidFor extends LiquidNode {
|
|
|
|
private String varName;
|
|
|
|
private String varName;
|
|
|
|
private String condition;
|
|
|
|
private String condition;
|
|
|
|
private ExpressionNode compiled;
|
|
|
|
private ExpressionNode compiled;
|
|
|
|
|
|
|
|
private boolean reversed = false;
|
|
|
|
|
|
|
|
private int limit = -1;
|
|
|
|
|
|
|
|
private int offset = -1;
|
|
|
|
private List<LiquidNode> body = new ArrayList<>();
|
|
|
|
private List<LiquidNode> body = new ArrayList<>();
|
|
|
|
|
|
|
|
private List<LiquidNode> elseBody = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
if (compiled == null)
|
|
|
|
if (compiled == null) {
|
|
|
|
compiled = engine.parse(condition);
|
|
|
|
ExpressionNodeWithOffset po = engine.parsePartial(condition, 0);
|
|
|
|
List<Base> list = engine.evaluate(ctxt, resource, resource, resource, compiled);
|
|
|
|
compiled = po.getNode();
|
|
|
|
LiquidEngineContext lctxt = new LiquidEngineContext(ctxt);
|
|
|
|
if (po.getOffset() < condition.length()) {
|
|
|
|
for (Base o : list) {
|
|
|
|
parseModifiers(condition.substring(po.getOffset()));
|
|
|
|
lctxt.vars.put(varName, o);
|
|
|
|
|
|
|
|
for (LiquidNode n : body) {
|
|
|
|
|
|
|
|
n.evaluate(b, resource, lctxt);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Base> list = engine.evaluate(ctxt, resource, resource, resource, compiled);
|
|
|
|
|
|
|
|
LiquidEngineContext lctxt = new LiquidEngineContext(ctxt);
|
|
|
|
|
|
|
|
if (list.isEmpty()) {
|
|
|
|
|
|
|
|
for (LiquidNode n : elseBody) {
|
|
|
|
|
|
|
|
n.evaluate(b, resource, lctxt);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (reversed) {
|
|
|
|
|
|
|
|
Collections.reverse(list);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
for (Base o : list) {
|
|
|
|
|
|
|
|
if (offset >= 0 && i < offset) {
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (limit >= 0 && i == limit) {
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
lctxt.vars.put(varName, o);
|
|
|
|
|
|
|
|
boolean wantBreak = false;
|
|
|
|
|
|
|
|
for (LiquidNode n : body) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
n.evaluate(b, resource, lctxt);
|
|
|
|
|
|
|
|
} catch (LiquidContinueExecuted e) {
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
} catch (LiquidBreakExecuted e) {
|
|
|
|
|
|
|
|
wantBreak = true;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wantBreak) {
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void parseModifiers(String cnt) {
|
|
|
|
|
|
|
|
String src = cnt;
|
|
|
|
|
|
|
|
while (!Utilities.noString(cnt)) {
|
|
|
|
|
|
|
|
if (cnt.startsWith("reversed")) {
|
|
|
|
|
|
|
|
reversed = true;
|
|
|
|
|
|
|
|
cnt = cnt.substring(8);
|
|
|
|
|
|
|
|
} else if (cnt.startsWith("limit")) {
|
|
|
|
|
|
|
|
cnt = cnt.substring(5).trim();
|
|
|
|
|
|
|
|
if (!cnt.startsWith(":")) {
|
|
|
|
|
|
|
|
throw new FHIRException("Exception evaluating "+src+": limit is not followed by ':'");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
cnt = cnt.substring(1).trim();
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
while (i < cnt.length() && Character.isDigit(cnt.charAt(i))) {
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i == 0) {
|
|
|
|
|
|
|
|
throw new FHIRException("Exception evaluating "+src+": limit is not followed by a number");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
limit = Integer.parseInt(cnt.substring(0, i));
|
|
|
|
|
|
|
|
cnt = cnt.substring(i);
|
|
|
|
|
|
|
|
} else if (cnt.startsWith("offset")) {
|
|
|
|
|
|
|
|
cnt = cnt.substring(6).trim();
|
|
|
|
|
|
|
|
if (!cnt.startsWith(":")) {
|
|
|
|
|
|
|
|
throw new FHIRException("Exception evaluating "+src+": limit is not followed by ':'");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
cnt = cnt.substring(1).trim();
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
while (i < cnt.length() && Character.isDigit(cnt.charAt(i))) {
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i == 0) {
|
|
|
|
|
|
|
|
throw new FHIRException("Exception evaluating "+src+": limit is not followed by a number");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = Integer.parseInt(cnt.substring(0, i));
|
|
|
|
|
|
|
|
cnt = cnt.substring(i);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
throw new FHIRException("Exception evaluating "+src+": unexpected content at "+cnt);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -185,11 +329,11 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
private Map<String, ExpressionNode> params = new HashMap<>();
|
|
|
|
private Map<String, ExpressionNode> params = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public void evaluate(StringBuilder b, Resource resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
|
|
|
|
String src = includeResolver.fetchInclude(LiquidEngine.this, page);
|
|
|
|
String src = includeResolver.fetchInclude(LiquidEngine.this, page);
|
|
|
|
LiquidParser parser = new LiquidParser(src);
|
|
|
|
LiquidParser parser = new LiquidParser(src);
|
|
|
|
LiquidDocument doc = parser.parse(page);
|
|
|
|
LiquidDocument doc = parser.parse(page);
|
|
|
|
LiquidEngineContext nctxt = new LiquidEngineContext(ctxt.externalContext);
|
|
|
|
LiquidEngineContext nctxt = new LiquidEngineContext(ctxt.externalContext);
|
|
|
|
Tuple incl = new Tuple();
|
|
|
|
Tuple incl = new Tuple();
|
|
|
|
nctxt.vars.put("include", incl);
|
|
|
|
nctxt.vars.put("include", incl);
|
|
|
|
for (String s : params.keySet()) {
|
|
|
|
for (String s : params.keySet()) {
|
|
|
@ -201,7 +345,7 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static class LiquidDocument {
|
|
|
|
public static class LiquidDocument {
|
|
|
|
private List<LiquidNode> body = new ArrayList<>();
|
|
|
|
private List<LiquidNode> body = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -225,64 +369,124 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private char next2() {
|
|
|
|
private char next2() {
|
|
|
|
if (cursor >= source.length()-1)
|
|
|
|
if (cursor >= source.length() - 1)
|
|
|
|
return 0;
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
else
|
|
|
|
return source.charAt(cursor+1);
|
|
|
|
return source.charAt(cursor + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private char grab() {
|
|
|
|
private char grab() {
|
|
|
|
cursor++;
|
|
|
|
cursor++;
|
|
|
|
return source.charAt(cursor-1);
|
|
|
|
return source.charAt(cursor - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public LiquidDocument parse(String name) throws FHIRException {
|
|
|
|
public LiquidDocument parse(String name) throws FHIRException {
|
|
|
|
this.name = name;
|
|
|
|
this.name = name;
|
|
|
|
LiquidDocument doc = new LiquidDocument();
|
|
|
|
LiquidDocument doc = new LiquidDocument();
|
|
|
|
parseList(doc.body, new String[0]);
|
|
|
|
parseList(doc.body, false, new String[0]);
|
|
|
|
return doc;
|
|
|
|
return doc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String parseList(List<LiquidNode> list, String[] terminators) throws FHIRException {
|
|
|
|
public LiquidCycle parseCycle(String cnt) {
|
|
|
|
|
|
|
|
LiquidCycle res = new LiquidCycle();
|
|
|
|
|
|
|
|
cnt = "," + cnt.substring(5).trim();
|
|
|
|
|
|
|
|
while (!Utilities.noString(cnt)) {
|
|
|
|
|
|
|
|
if (!cnt.startsWith(",")) {
|
|
|
|
|
|
|
|
throw new FHIRException("Script " + name + ": Script " + name + ": Found " + cnt.charAt(0) + " expecting ',' parsing cycle");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
cnt = cnt.substring(1).trim();
|
|
|
|
|
|
|
|
if (!cnt.startsWith("\"")) {
|
|
|
|
|
|
|
|
throw new FHIRException("Script " + name + ": Script " + name + ": Found " + cnt.charAt(0) + " expecting '\"' parsing cycle");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
cnt = cnt.substring(1);
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
while (i < cnt.length() && cnt.charAt(i) != '"') {
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i == cnt.length()) {
|
|
|
|
|
|
|
|
throw new FHIRException("Script " + name + ": Script " + name + ": Found unterminated string parsing cycle");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
res.list.add(cnt.substring(0, i));
|
|
|
|
|
|
|
|
cnt = cnt.substring(i + 1).trim();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String parseList(List<LiquidNode> list, boolean inLoop, String[] terminators) throws FHIRException {
|
|
|
|
String close = null;
|
|
|
|
String close = null;
|
|
|
|
while (cursor < source.length()) {
|
|
|
|
while (cursor < source.length()) {
|
|
|
|
if (next1() == '{' && (next2() == '%' || next2() == '{' )) {
|
|
|
|
if (next1() == '{' && (next2() == '%' || next2() == '{')) {
|
|
|
|
if (next2() == '%') {
|
|
|
|
if (next2() == '%') {
|
|
|
|
String cnt = parseTag('%');
|
|
|
|
String cnt = parseTag('%');
|
|
|
|
if (Utilities.existsInList(cnt, terminators)) {
|
|
|
|
if (isTerminator(cnt, terminators)) {
|
|
|
|
close = cnt;
|
|
|
|
close = cnt;
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
} else if (cnt.startsWith("if "))
|
|
|
|
} else if (cnt.startsWith("if "))
|
|
|
|
list.add(parseIf(cnt));
|
|
|
|
list.add(parseIf(cnt, inLoop));
|
|
|
|
else if (cnt.startsWith("loop "))
|
|
|
|
else if (cnt.startsWith("loop ")) // loop is deprecated, but still
|
|
|
|
|
|
|
|
// supported
|
|
|
|
list.add(parseLoop(cnt.substring(4).trim()));
|
|
|
|
list.add(parseLoop(cnt.substring(4).trim()));
|
|
|
|
|
|
|
|
else if (cnt.startsWith("for "))
|
|
|
|
|
|
|
|
list.add(parseFor(cnt.substring(3).trim()));
|
|
|
|
|
|
|
|
else if (inLoop && cnt.equals("continue"))
|
|
|
|
|
|
|
|
list.add(new LiquidContinue());
|
|
|
|
|
|
|
|
else if (inLoop && cnt.equals("break"))
|
|
|
|
|
|
|
|
list.add(new LiquidBreak());
|
|
|
|
|
|
|
|
else if (inLoop && cnt.startsWith("cycle "))
|
|
|
|
|
|
|
|
list.add(parseCycle(cnt));
|
|
|
|
else if (cnt.startsWith("include "))
|
|
|
|
else if (cnt.startsWith("include "))
|
|
|
|
list.add(parseInclude(cnt.substring(7).trim()));
|
|
|
|
list.add(parseInclude(cnt.substring(7).trim()));
|
|
|
|
else
|
|
|
|
else
|
|
|
|
throw new FHIRException("Script "+name+": Script "+name+": Unknown flow control statement "+cnt);
|
|
|
|
throw new FHIRException("Script " + name + ": Script " + name + ": Unknown flow control statement " + cnt);
|
|
|
|
} else { // next2() == '{'
|
|
|
|
} else { // next2() == '{'
|
|
|
|
list.add(parseStatement());
|
|
|
|
list.add(parseStatement());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
if (list.size() == 0 || !(list.get(list.size()-1) instanceof LiquidConstant))
|
|
|
|
if (list.size() == 0 || !(list.get(list.size() - 1) instanceof LiquidConstant))
|
|
|
|
list.add(new LiquidConstant());
|
|
|
|
list.add(new LiquidConstant());
|
|
|
|
((LiquidConstant) list.get(list.size()-1)).addChar(grab());
|
|
|
|
((LiquidConstant) list.get(list.size() - 1)).addChar(grab());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (LiquidNode n : list)
|
|
|
|
for (LiquidNode n : list)
|
|
|
|
n.closeUp();
|
|
|
|
n.closeUp();
|
|
|
|
if (terminators.length > 0)
|
|
|
|
if (terminators.length > 0)
|
|
|
|
if (!Utilities.existsInList(close, terminators))
|
|
|
|
if (!isTerminator(close, terminators))
|
|
|
|
throw new FHIRException("Script "+name+": Script "+name+": Found end of script looking for "+terminators);
|
|
|
|
throw new FHIRException("Script " + name + ": Script " + name + ": Found end of script looking for " + terminators);
|
|
|
|
return close;
|
|
|
|
return close;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private LiquidNode parseIf(String cnt) throws FHIRException {
|
|
|
|
private boolean isTerminator(String cnt, String[] terminators) {
|
|
|
|
|
|
|
|
if (Utilities.noString(cnt)) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (String t : terminators) {
|
|
|
|
|
|
|
|
if (t.endsWith(" ")) {
|
|
|
|
|
|
|
|
if (cnt.startsWith(t)) {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (cnt.equals(t)) {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private LiquidNode parseIf(String cnt, boolean inLoop) throws FHIRException {
|
|
|
|
LiquidIf res = new LiquidIf();
|
|
|
|
LiquidIf res = new LiquidIf();
|
|
|
|
res.condition = cnt.substring(3).trim();
|
|
|
|
res.condition = cnt.substring(3).trim();
|
|
|
|
String term = parseList(res.thenBody, new String[] { "else", "endif"} );
|
|
|
|
String term = parseList(res.thenBody, inLoop, new String[] { "else", "elsif ", "endif" });
|
|
|
|
if ("else".equals(term))
|
|
|
|
while (term.startsWith("elsif ")) {
|
|
|
|
term = parseList(res.elseBody, new String[] { "endif"} );
|
|
|
|
LiquidElsIf elsIf = new LiquidElsIf();
|
|
|
|
|
|
|
|
res.elseIf.add(elsIf);
|
|
|
|
|
|
|
|
elsIf.condition = term.substring(5).trim();
|
|
|
|
|
|
|
|
term = parseList(elsIf.body, inLoop, new String[] { "elsif ", "else", "endif" });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ("else".equals(term)) {
|
|
|
|
|
|
|
|
term = parseList(res.elseBody, inLoop, new String[] { "endif" });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -291,7 +495,7 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
while (i < cnt.length() && !Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
while (i < cnt.length() && !Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
i++;
|
|
|
|
i++;
|
|
|
|
if (i == cnt.length() || i == 0)
|
|
|
|
if (i == cnt.length() || i == 0)
|
|
|
|
throw new FHIRException("Script "+name+": Error reading include: "+cnt);
|
|
|
|
throw new FHIRException("Script " + name + ": Error reading include: " + cnt);
|
|
|
|
LiquidInclude res = new LiquidInclude();
|
|
|
|
LiquidInclude res = new LiquidInclude();
|
|
|
|
res.page = cnt.substring(0, i);
|
|
|
|
res.page = cnt.substring(0, i);
|
|
|
|
while (i < cnt.length() && Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
while (i < cnt.length() && Character.isWhitespace(cnt.charAt(i)))
|
|
|
@ -300,27 +504,26 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
int j = i;
|
|
|
|
int j = i;
|
|
|
|
while (i < cnt.length() && cnt.charAt(i) != '=')
|
|
|
|
while (i < cnt.length() && cnt.charAt(i) != '=')
|
|
|
|
i++;
|
|
|
|
i++;
|
|
|
|
if (i >= cnt.length() || j == i)
|
|
|
|
if (i >= cnt.length() || j == i)
|
|
|
|
throw new FHIRException("Script "+name+": Error reading include: "+cnt);
|
|
|
|
throw new FHIRException("Script " + name + ": Error reading include: " + cnt);
|
|
|
|
String n = cnt.substring(j, i);
|
|
|
|
String n = cnt.substring(j, i);
|
|
|
|
if (res.params.containsKey(n))
|
|
|
|
if (res.params.containsKey(n))
|
|
|
|
throw new FHIRException("Script "+name+": Error reading include: "+cnt);
|
|
|
|
throw new FHIRException("Script " + name + ": Error reading include: " + cnt);
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
ExpressionNodeWithOffset t = engine.parsePartial(cnt, i);
|
|
|
|
|
|
|
|
i = t.getOffset();
|
|
|
|
|
|
|
|
res.params.put(n, t.getNode());
|
|
|
|
|
|
|
|
while (i < cnt.length() && Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
i++;
|
|
|
|
i++;
|
|
|
|
ExpressionNodeWithOffset t = engine.parsePartial(cnt, i);
|
|
|
|
|
|
|
|
i = t.getOffset();
|
|
|
|
|
|
|
|
res.params.put(n, t.getNode());
|
|
|
|
|
|
|
|
while (i < cnt.length() && Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private LiquidNode parseLoop(String cnt) throws FHIRException {
|
|
|
|
private LiquidNode parseLoop(String cnt) throws FHIRException {
|
|
|
|
int i = 0;
|
|
|
|
int i = 0;
|
|
|
|
while (!Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
while (!Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
i++;
|
|
|
|
i++;
|
|
|
|
LiquidLoop res = new LiquidLoop();
|
|
|
|
LiquidFor res = new LiquidFor();
|
|
|
|
res.varName = cnt.substring(0, i);
|
|
|
|
res.varName = cnt.substring(0, i);
|
|
|
|
while (Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
while (Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
i++;
|
|
|
|
i++;
|
|
|
@ -328,36 +531,58 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
while (!Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
while (!Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
i++;
|
|
|
|
i++;
|
|
|
|
if (!"in".equals(cnt.substring(j, i)))
|
|
|
|
if (!"in".equals(cnt.substring(j, i)))
|
|
|
|
throw new FHIRException("Script "+name+": Script "+name+": Error reading loop: "+cnt);
|
|
|
|
throw new FHIRException("Script " + name + ": Script " + name + ": Error reading loop: " + cnt);
|
|
|
|
res.condition = cnt.substring(i).trim();
|
|
|
|
res.condition = cnt.substring(i).trim();
|
|
|
|
parseList(res.body, new String[] { "endloop"} );
|
|
|
|
parseList(res.body, false, new String[] { "endloop" });
|
|
|
|
return res;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private LiquidNode parseFor(String cnt) throws FHIRException {
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
while (!Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
LiquidFor res = new LiquidFor();
|
|
|
|
|
|
|
|
res.varName = cnt.substring(0, i);
|
|
|
|
|
|
|
|
while (Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
int j = i;
|
|
|
|
|
|
|
|
while (!Character.isWhitespace(cnt.charAt(i)))
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
|
|
if (!"in".equals(cnt.substring(j, i)))
|
|
|
|
|
|
|
|
throw new FHIRException("Script " + name + ": Script " + name + ": Error reading loop: " + cnt);
|
|
|
|
|
|
|
|
res.condition = cnt.substring(i).trim();
|
|
|
|
|
|
|
|
String term = parseList(res.body, true, new String[] { "endfor", "else" });
|
|
|
|
|
|
|
|
if ("else".equals(term)) {
|
|
|
|
|
|
|
|
parseList(res.elseBody, false, new String[] { "endfor" });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String parseTag(char ch) throws FHIRException {
|
|
|
|
private String parseTag(char ch) throws FHIRException {
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
while (cursor < source.length() && !(next1() == '%' && next2() == '}')) {
|
|
|
|
while (cursor < source.length() && !(next1() == '%' && next2() == '}')) {
|
|
|
|
b.append(grab());
|
|
|
|
b.append(grab());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!(next1() == '%' && next2() == '}'))
|
|
|
|
if (!(next1() == '%' && next2() == '}'))
|
|
|
|
throw new FHIRException("Script "+name+": Unterminated Liquid statement {% "+b.toString());
|
|
|
|
throw new FHIRException("Script " + name + ": Unterminated Liquid statement {% " + b.toString());
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
return b.toString().trim();
|
|
|
|
return b.toString().trim();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private LiquidStatement parseStatement() throws FHIRException {
|
|
|
|
private LiquidStatement parseStatement() throws FHIRException {
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
while (cursor < source.length() && !(next1() == '}' && next2() == '}')) {
|
|
|
|
while (cursor < source.length() && !(next1() == '}' && next2() == '}')) {
|
|
|
|
b.append(grab());
|
|
|
|
b.append(grab());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!(next1() == '}' && next2() == '}'))
|
|
|
|
if (!(next1() == '}' && next2() == '}'))
|
|
|
|
throw new FHIRException("Script "+name+": Unterminated Liquid statement {{ "+b.toString());
|
|
|
|
throw new FHIRException("Script " + name + ": Unterminated Liquid statement {{ " + b.toString());
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
grab();
|
|
|
|
LiquidStatement res = new LiquidStatement();
|
|
|
|
LiquidStatement res = new LiquidStatement();
|
|
|
|
res.statement = b.toString().trim();
|
|
|
|
res.statement = b.toString().trim();
|
|
|
@ -471,7 +696,7 @@ public class LiquidEngine implements IEvaluationContext {
|
|
|
|
if (!cnt.equals(node.getAttributes().get(an))) {
|
|
|
|
if (!cnt.equals(node.getAttributes().get(an))) {
|
|
|
|
node.getAttributes().put(an, cnt);
|
|
|
|
node.getAttributes().put(an, cnt);
|
|
|
|
replaced = true;
|
|
|
|
replaced = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return replaced;
|
|
|
|
return replaced;
|
|
|
|