Merge pull request #1840 from hapifhir/2024-12-gg-liquid-refactor
2024 12 gg liquid refactor
This commit is contained in:
commit
2cdef8f63d
|
@ -43,7 +43,7 @@ public class LiquidEngineTests implements ILiquidEngineIncludeResolver {
|
|||
|
||||
public static Stream<Arguments> data() throws ParserConfigurationException, SAXException, IOException {
|
||||
testdoc = (JsonObject) new com.google.gson.JsonParser()
|
||||
.parse(TestingUtilities.loadTestResource("r5", "liquid", "liquid-tests.json"));
|
||||
.parse(TestingUtilities.loadTestResource("r4b", "liquid", "liquid-tests.json"));
|
||||
JsonArray tests = testdoc.getAsJsonArray("tests");
|
||||
List<Arguments> objects = new ArrayList<>();
|
||||
for (JsonElement n : tests) {
|
||||
|
|
|
@ -30,13 +30,13 @@ import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus;
|
|||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
|
||||
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine.LiquidDocument;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.hl7.fhir.r5.model.Tuple;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.utils.EOperationOutcome;
|
||||
import org.hl7.fhir.r5.utils.LiquidEngine;
|
||||
import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument;
|
||||
import org.hl7.fhir.utilities.FhirPublication;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
|
|
@ -189,6 +189,13 @@ public class FHIRPathEngine {
|
|||
// the application can implement them by providing a constant resolver
|
||||
public interface IEvaluationContext {
|
||||
|
||||
public abstract class FunctionDefinition {
|
||||
public abstract String name();
|
||||
public abstract FunctionDetails details();
|
||||
public abstract TypeDetails check(FHIRPathEngine engine, Object appContext, TypeDetails focus, List<TypeDetails> parameters);
|
||||
public abstract List<Base> execute(FHIRPathEngine engine, Object appContext, List<Base> focus, List<List<Base>> parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* A constant reference - e.g. a reference to a name that must be resolved in context.
|
||||
* The % will be removed from the constant name before this is invoked.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package org.hl7.fhir.r5.utils;
|
||||
package org.hl7.fhir.r5.liquid;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.hl7.fhir.r5.utils;
|
||||
package org.hl7.fhir.r5.liquid;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
|
@ -83,5 +83,15 @@ public class BaseJsonWrapper extends Base {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isPrimitive() {
|
||||
return j.isJsonPrimitive();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String primitiveValue() {
|
||||
return toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package org.hl7.fhir.r5.liquid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext.FunctionDefinition;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.DateTimeType;
|
||||
import org.hl7.fhir.r5.model.IntegerType;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.hl7.fhir.utilities.FhirPublication;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
import com.microsoft.schemas.office.visio.x2012.main.impl.FunctionDefTypeImpl;
|
||||
|
||||
public class GlobalObject extends Base {
|
||||
|
||||
private DateTimeType dt;
|
||||
private StringType pathToSpec;
|
||||
|
||||
public GlobalObject(DateTimeType td, StringType pathToSpec) {
|
||||
super();
|
||||
this.dt = td;
|
||||
this.pathToSpec = pathToSpec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fhirType() {
|
||||
return "GlobalObject";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdBase() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdBase(String value) {
|
||||
throw new Error("Read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FhirPublication getFHIRPublicationVersion() {
|
||||
return FhirPublication.R5;
|
||||
}
|
||||
|
||||
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
|
||||
if ("dateTime".equals(name)) {
|
||||
return wrap(dt);
|
||||
} else if ("path".equals(name)) {
|
||||
return wrap(pathToSpec);
|
||||
} else {
|
||||
return super.getProperty(hash, name, checkValid);
|
||||
}
|
||||
}
|
||||
|
||||
private Base[] wrap(Base b) {
|
||||
Base[] l = new Base[1];
|
||||
l[0] = b;
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Base> executeFunction(FHIRPathEngine engine, Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class GlobalObjectRandomFunction extends FunctionDefinition {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "random";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionDetails details() {
|
||||
return new FunctionDetails("Generate a Random Number", 1, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails check(FHIRPathEngine engine, Object appContext, TypeDetails focus, List<TypeDetails> parameters) {
|
||||
if (focus.hasType("GlobalObject")) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, "integer");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Base> execute(FHIRPathEngine engine, Object appContext, List<Base> focus, List<List<Base>> parameters) {
|
||||
List<Base> list = new ArrayList<>();
|
||||
int scale = Utilities.parseInt(parameters.get(0).get(0).primitiveValue(), 100)+ 1;
|
||||
list.add(new IntegerType((int)(Math.random() * scale)));
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.hl7.fhir.r5.utils;
|
||||
package org.hl7.fhir.r5.liquid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -42,22 +42,114 @@ import org.hl7.fhir.r5.context.IWorkerContext;
|
|||
import org.hl7.fhir.r5.fhirpath.ExpressionNode;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRLexer;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.ExpressionNodeWithOffset;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.BooleanType;
|
||||
import org.hl7.fhir.r5.model.IntegerType;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.hl7.fhir.r5.model.Tuple;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.utilities.FhirPublication;
|
||||
import org.hl7.fhir.utilities.MarkDownProcessor;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
|
||||
public class LiquidEngine implements IEvaluationContext {
|
||||
|
||||
public static class LiquidForLoopObject extends Base {
|
||||
|
||||
private static final long serialVersionUID = 6951452522873320076L;
|
||||
private boolean first;
|
||||
private int index;
|
||||
private int index0;
|
||||
private int rindex;
|
||||
private int rindex0;
|
||||
private boolean last;
|
||||
private int length;
|
||||
private LiquidForLoopObject parentLoop;
|
||||
|
||||
|
||||
public LiquidForLoopObject(int size, int i, int offset, int limit, LiquidForLoopObject parentLoop) {
|
||||
super();
|
||||
this.parentLoop = parentLoop;
|
||||
if (offset == -1) {
|
||||
offset = 0;
|
||||
}
|
||||
if (limit == -1) {
|
||||
limit = size;
|
||||
}
|
||||
|
||||
first = i == offset;
|
||||
index = i+1-offset;
|
||||
index0 = i-offset;
|
||||
rindex = (limit-offset) - 1 - i;
|
||||
rindex0 = (limit-offset) - i;
|
||||
length = limit-offset;
|
||||
last = i == (limit-offset)-1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getIdBase() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdBase(String value) {
|
||||
throw new Error("forLoop is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base copy() {
|
||||
throw new Error("forLoop is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FhirPublication getFHIRPublicationVersion() {
|
||||
return FhirPublication.R5;
|
||||
}
|
||||
|
||||
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
|
||||
switch (name) {
|
||||
case "parentLoop" : return wrap(parentLoop);
|
||||
case "first" : return wrap(new BooleanType(first));
|
||||
case "last" : return wrap(new BooleanType(last));
|
||||
case "index" : return wrap(new IntegerType(index));
|
||||
case "index0" : return wrap(new IntegerType(index0));
|
||||
case "rindex" : return wrap(new IntegerType(rindex));
|
||||
case "rindex0" : return wrap(new IntegerType(rindex0));
|
||||
case "length" : return wrap(new IntegerType(length));
|
||||
}
|
||||
|
||||
return super.getProperty(hash, name, checkValid);
|
||||
}
|
||||
|
||||
private Base[] wrap(Base b) {
|
||||
Base[] l = new Base[1];
|
||||
l[0] = b;
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "forLoop";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String fhirType() {
|
||||
return "ForLoop";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface ILiquidRenderingSupport {
|
||||
String renderForLiquid(Object appContext, Base i) throws FHIRException;
|
||||
}
|
||||
|
@ -71,16 +163,18 @@ public class LiquidEngine implements IEvaluationContext {
|
|||
private ILiquidEngineIncludeResolver includeResolver;
|
||||
private ILiquidRenderingSupport renderingSupport;
|
||||
private MarkDownProcessor processor = new MarkDownProcessor(Dialect.COMMON_MARK);
|
||||
private Map<String, Base> vars = new HashMap<>();
|
||||
|
||||
private class LiquidEngineContext {
|
||||
private Object externalContext;
|
||||
private Map<String, Base> loopVars = new HashMap<>();
|
||||
private Map<String, Base> globalVars = new HashMap<>();
|
||||
|
||||
public LiquidEngineContext(Object externalContext) {
|
||||
public LiquidEngineContext(Object externalContext, Map<String, Base> vars) {
|
||||
super();
|
||||
this.externalContext = externalContext;
|
||||
globalVars = new HashMap<>();
|
||||
globalVars.putAll(vars);
|
||||
}
|
||||
|
||||
public LiquidEngineContext(Object externalContext, LiquidEngineContext existing) {
|
||||
|
@ -122,13 +216,17 @@ public class LiquidEngine implements IEvaluationContext {
|
|||
this.renderingSupport = renderingSupport;
|
||||
}
|
||||
|
||||
public Map<String, Base> getVars() {
|
||||
return vars;
|
||||
}
|
||||
|
||||
public LiquidDocument parse(String source, String sourceName) throws FHIRException {
|
||||
return new LiquidParser(source).parse(sourceName);
|
||||
}
|
||||
|
||||
public String evaluate(LiquidDocument document, Base resource, Object appContext) throws FHIRException {
|
||||
StringBuilder b = new StringBuilder();
|
||||
LiquidEngineContext ctxt = new LiquidEngineContext(appContext);
|
||||
LiquidEngineContext ctxt = new LiquidEngineContext(appContext, vars );
|
||||
for (LiquidNode n : document.body) {
|
||||
n.evaluate(b, resource, ctxt);
|
||||
}
|
||||
|
@ -403,6 +501,7 @@ public class LiquidEngine implements IEvaluationContext {
|
|||
Collections.reverse(list);
|
||||
}
|
||||
int i = 0;
|
||||
LiquidForLoopObject parentLoop = (LiquidForLoopObject) lctxt.globalVars.get("forLoop");
|
||||
for (Base o : list) {
|
||||
if (offset >= 0 && i < offset) {
|
||||
i++;
|
||||
|
@ -411,6 +510,8 @@ public class LiquidEngine implements IEvaluationContext {
|
|||
if (limit >= 0 && i == limit) {
|
||||
break;
|
||||
}
|
||||
LiquidForLoopObject forloop = new LiquidForLoopObject(list.size(), i, offset, limit, parentLoop);
|
||||
lctxt.globalVars.put("forLoop", forloop);
|
||||
if (lctxt.globalVars.containsKey(varName)) {
|
||||
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_VARIABLE_ALREADY_ASSIGNED, varName));
|
||||
}
|
||||
|
@ -431,6 +532,7 @@ public class LiquidEngine implements IEvaluationContext {
|
|||
}
|
||||
i++;
|
||||
}
|
||||
lctxt.globalVars.put("forLoop", parentLoop);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -477,6 +579,20 @@ public class LiquidEngine implements IEvaluationContext {
|
|||
}
|
||||
}
|
||||
|
||||
private class LiquidCapture extends LiquidNode {
|
||||
private String varName;
|
||||
private List<LiquidNode> body = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
|
||||
StringBuilder bc = new StringBuilder();
|
||||
for (LiquidNode n : body) {
|
||||
n.evaluate(bc, resource, ctxt);
|
||||
}
|
||||
ctxt.globalVars.put(varName, new StringType(bc.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private class LiquidInclude extends LiquidNode {
|
||||
private String page;
|
||||
private Map<String, ExpressionNode> params = new HashMap<>();
|
||||
|
@ -600,6 +716,8 @@ public class LiquidEngine implements IEvaluationContext {
|
|||
list.add(parseInclude(cnt.substring(7).trim()));
|
||||
else if (cnt.startsWith("assign "))
|
||||
list.add(parseAssign(cnt.substring(6).trim()));
|
||||
else if (cnt.startsWith("capture "))
|
||||
list.add(parseCapture(cnt.substring(7).trim()));
|
||||
else
|
||||
throw new FHIRException(engine.getWorker().formatMessage(I18nConstants.LIQUID_UNKNOWN_FLOW_STMT,name, cnt));
|
||||
} else { // next2() == '{'
|
||||
|
@ -728,6 +846,16 @@ public class LiquidEngine implements IEvaluationContext {
|
|||
return res;
|
||||
}
|
||||
|
||||
private LiquidNode parseCapture(String cnt) throws FHIRException {
|
||||
int i = 0;
|
||||
while (i < cnt.length() && !Character.isWhitespace(cnt.charAt(i)))
|
||||
i++;
|
||||
LiquidCapture res = new LiquidCapture();
|
||||
res.varName = cnt.substring(0, i);
|
||||
parseList(res.body, true, new String[] { "endcapture" });
|
||||
return res;
|
||||
}
|
||||
|
||||
private LiquidNode parseAssign(String cnt) throws FHIRException {
|
||||
int i = 0;
|
||||
while (!Character.isWhitespace(cnt.charAt(i)))
|
|
@ -36,7 +36,7 @@ import java.util.Map;
|
|||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.utilities.FhirPublication;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
|
@ -629,4 +629,10 @@ public abstract class Base implements Serializable, IBase, IElement {
|
|||
}
|
||||
|
||||
public abstract FhirPublication getFHIRPublicationVersion();
|
||||
|
||||
public List<Base> executeFunction(FHIRPathEngine engine, Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -7,14 +7,14 @@ import org.hl7.fhir.exceptions.DefinitionException;
|
|||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine.ILiquidRenderingSupport;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine.LiquidDocument;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.DataType;
|
||||
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
|
||||
import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
|
||||
import org.hl7.fhir.r5.utils.EOperationOutcome;
|
||||
import org.hl7.fhir.r5.utils.LiquidEngine;
|
||||
import org.hl7.fhir.r5.utils.LiquidEngine.ILiquidRenderingSupport;
|
||||
import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument;
|
||||
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
|
|
|
@ -11,11 +11,11 @@ import javax.xml.parsers.ParserConfigurationException;
|
|||
import org.apache.commons.collections4.map.HashedMap;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r5.formats.XmlParser;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine.ILiquidEngineIncludeResolver;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine.LiquidDocument;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.test.utils.TestingUtilities;
|
||||
import org.hl7.fhir.r5.utils.LiquidEngine;
|
||||
import org.hl7.fhir.r5.utils.LiquidEngine.ILiquidEngineIncludeResolver;
|
||||
import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
|
|
@ -12,11 +12,13 @@ import org.hl7.fhir.r5.context.IWorkerContext;
|
|||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
|
||||
import org.hl7.fhir.r5.liquid.BaseJsonWrapper;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine.LiquidDocument;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.test.utils.TestingUtilities;
|
||||
import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument;
|
||||
import org.hl7.fhir.utilities.json.JsonException;
|
||||
import org.hl7.fhir.utilities.json.model.JsonObject;
|
||||
import org.hl7.fhir.utilities.json.parser.JsonParser;
|
||||
|
|
|
@ -4320,7 +4320,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
Collections.sort(list, new StructureDefinitionSorterByUrl());
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (StructureDefinition sd : list) {
|
||||
b.append(sd.getUrl());
|
||||
b.append(sd.getVersionedUrl());
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue