Merge pull request #1138 from hapifhir/gg-202302-map-validation
FML validation
This commit is contained in:
commit
edf5ecf275
|
@ -129,6 +129,7 @@ public class Element extends Base {
|
||||||
private int instanceId;
|
private int instanceId;
|
||||||
private boolean isNull;
|
private boolean isNull;
|
||||||
private Base source;
|
private Base source;
|
||||||
|
private boolean ignorePropertyOrder;
|
||||||
|
|
||||||
public Element(String name) {
|
public Element(String name) {
|
||||||
super();
|
super();
|
||||||
|
@ -148,6 +149,9 @@ public class Element extends Base {
|
||||||
super();
|
super();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.property = property;
|
this.property = property;
|
||||||
|
if (property.isResource()) {
|
||||||
|
children = new ArrayList<>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Element(String name, Property property, String type, String value) {
|
public Element(String name, Property property, String type, String value) {
|
||||||
|
@ -211,8 +215,9 @@ public class Element extends Base {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setType(String type) {
|
public Element setType(String type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,7 +603,7 @@ public class Element extends Base {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String primitiveValue() {
|
public String primitiveValue() {
|
||||||
if (isPrimitive())
|
if (isPrimitive() || value != null)
|
||||||
return value;
|
return value;
|
||||||
else {
|
else {
|
||||||
if (hasPrimitiveValue() && children != null) {
|
if (hasPrimitiveValue() && children != null) {
|
||||||
|
@ -1360,6 +1365,19 @@ public class Element extends Base {
|
||||||
setChildValue(name, value.primitiveValue());
|
setChildValue(name, value.primitiveValue());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isIgnorePropertyOrder() {
|
||||||
|
return ignorePropertyOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIgnorePropertyOrder(boolean ignorePropertyOrder) {
|
||||||
|
this.ignorePropertyOrder = ignorePropertyOrder;
|
||||||
|
if (children != null) {
|
||||||
|
for (Element e : children) {
|
||||||
|
e.setIgnorePropertyOrder(ignorePropertyOrder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,570 @@
|
||||||
|
package org.hl7.fhir.r5.elementmodel;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.exceptions.DefinitionException;
|
||||||
|
import org.hl7.fhir.exceptions.FHIRException;
|
||||||
|
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||||
|
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||||
|
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
||||||
|
import org.hl7.fhir.r5.model.ExpressionNode;
|
||||||
|
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupUnmappedMode;
|
||||||
|
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
|
||||||
|
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
|
||||||
|
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupTypeMode;
|
||||||
|
import org.hl7.fhir.r5.model.StructureMap.StructureMapTransform;
|
||||||
|
import org.hl7.fhir.r5.utils.FHIRLexer;
|
||||||
|
import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException;
|
||||||
|
import org.hl7.fhir.r5.utils.FHIRPathEngine;
|
||||||
|
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
|
||||||
|
import org.hl7.fhir.utilities.SourceLocation;
|
||||||
|
import org.hl7.fhir.utilities.TextFile;
|
||||||
|
import org.hl7.fhir.utilities.Utilities;
|
||||||
|
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||||
|
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||||
|
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||||
|
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||||
|
|
||||||
|
public class FmlParser extends ParserBase {
|
||||||
|
|
||||||
|
private FHIRPathEngine fpe;
|
||||||
|
|
||||||
|
public FmlParser(IWorkerContext context) {
|
||||||
|
super(context);
|
||||||
|
fpe = new FHIRPathEngine(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<NamedElement> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException {
|
||||||
|
String text = TextFile.streamToString(stream);
|
||||||
|
List<NamedElement> result = new ArrayList<>();
|
||||||
|
result.add(new NamedElement(null, parse(text)));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compose(Element e, OutputStream destination, OutputStyle style, String base)
|
||||||
|
throws FHIRException, IOException {
|
||||||
|
throw new Error("Not done yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Element parse(String text) throws FHIRException {
|
||||||
|
FHIRLexer lexer = new FHIRLexer(text, "source");
|
||||||
|
if (lexer.done())
|
||||||
|
throw lexer.error("Map Input cannot be empty");
|
||||||
|
lexer.token("map");
|
||||||
|
Element result = Manager.build(context, context.fetchTypeDefinition("StructureMap"));
|
||||||
|
try {
|
||||||
|
result.makeElement("url").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("url"));
|
||||||
|
lexer.token("=");
|
||||||
|
result.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("name"));
|
||||||
|
if (lexer.hasComments()) {
|
||||||
|
result.makeElement("description").markLocation(lexer.getCurrentLocation()).setValue(lexer.getAllComments());
|
||||||
|
}
|
||||||
|
while (lexer.hasToken("conceptmap"))
|
||||||
|
parseConceptMap(result, lexer);
|
||||||
|
|
||||||
|
while (lexer.hasToken("uses"))
|
||||||
|
parseUses(result, lexer);
|
||||||
|
while (lexer.hasToken("imports"))
|
||||||
|
parseImports(result, lexer);
|
||||||
|
|
||||||
|
while (!lexer.done()) {
|
||||||
|
parseGroup(result, lexer);
|
||||||
|
}
|
||||||
|
} catch (FHIRLexerException e) {
|
||||||
|
logError("2023-02-24", e.getLocation().getLine(), e.getLocation().getColumn(), "??", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logError("2023-02-24", -1, -1, "?", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.hasChild("status")) {
|
||||||
|
result.makeElement("status").setValue("draft");
|
||||||
|
}
|
||||||
|
result.setIgnorePropertyOrder(true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseConceptMap(Element result, FHIRLexer lexer) throws FHIRLexerException {
|
||||||
|
lexer.token("conceptmap");
|
||||||
|
Element map = Manager.build(context, context.fetchTypeDefinition("ConceptMap"));
|
||||||
|
Element eid = map.makeElement("id").markLocation(lexer.getCurrentLocation());
|
||||||
|
String id = lexer.readConstant("map id");
|
||||||
|
if (id.startsWith("#"))
|
||||||
|
throw lexer.error("Concept Map identifier must start with #");
|
||||||
|
eid.setValue(id);
|
||||||
|
map.makeElement("status").setValue(PublicationStatus.DRAFT.toCode()); // todo: how to add this to the text format
|
||||||
|
result.makeElement("contained").setElement("resource", map);
|
||||||
|
lexer.token("{");
|
||||||
|
// lexer.token("source");
|
||||||
|
// map.setSource(new UriType(lexer.readConstant("source")));
|
||||||
|
// lexer.token("target");
|
||||||
|
// map.setSource(new UriType(lexer.readConstant("target")));
|
||||||
|
Map<String, String> prefixes = new HashMap<String, String>();
|
||||||
|
while (lexer.hasToken("prefix")) {
|
||||||
|
lexer.token("prefix");
|
||||||
|
String n = lexer.take();
|
||||||
|
lexer.token("=");
|
||||||
|
String v = lexer.readConstant("prefix url");
|
||||||
|
prefixes.put(n, v);
|
||||||
|
}
|
||||||
|
while (lexer.hasToken("unmapped")) {
|
||||||
|
lexer.token("unmapped");
|
||||||
|
lexer.token("for");
|
||||||
|
String n = readPrefix(prefixes, lexer);
|
||||||
|
Element g = getGroupE(map, n, null);
|
||||||
|
lexer.token("=");
|
||||||
|
SourceLocation loc = lexer.getCurrentLocation();
|
||||||
|
String v = lexer.take();
|
||||||
|
if (v.equals("provided")) {
|
||||||
|
g.makeElement("unmapped").makeElement("mode").markLocation(loc).setValue(ConceptMapGroupUnmappedMode.USESOURCECODE.toCode());
|
||||||
|
} else
|
||||||
|
throw lexer.error("Only unmapped mode PROVIDED is supported at this time");
|
||||||
|
}
|
||||||
|
while (!lexer.hasToken("}")) {
|
||||||
|
String srcs = readPrefix(prefixes, lexer);
|
||||||
|
lexer.token(":");
|
||||||
|
SourceLocation scloc = lexer.getCurrentLocation();
|
||||||
|
String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take();
|
||||||
|
SourceLocation relLoc = lexer.getCurrentLocation();
|
||||||
|
ConceptMapRelationship rel = readRelationship(lexer);
|
||||||
|
String tgts = readPrefix(prefixes, lexer);
|
||||||
|
Element g = getGroupE(map, srcs, tgts);
|
||||||
|
Element e = g.addElement("element");
|
||||||
|
e.makeElement("code").markLocation(scloc).setValue(sc.startsWith("\"") ? lexer.processConstant(sc) : sc);
|
||||||
|
Element tgt = e.addElement("target");
|
||||||
|
tgt.makeElement("relationship").markLocation(relLoc).setValue(rel.toCode());
|
||||||
|
lexer.token(":");
|
||||||
|
tgt.makeElement("code").markLocation(lexer.getCurrentLocation()).setValue(lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take());
|
||||||
|
if (lexer.hasComments()) {
|
||||||
|
tgt.makeElement("comment").markLocation(lexer.getCommentLocation()).setValue(lexer.getFirstComment());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lexer.token("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Element getGroupE(Element map, String srcs, String tgts) {
|
||||||
|
for (Element grp : map.getChildrenByName("group")) {
|
||||||
|
if (grp.getChildValue("source").equals(srcs)) {
|
||||||
|
Element tgt = grp.getNamedChild("target");
|
||||||
|
if (tgt == null || tgts == null || tgts.equals(tgt.getValue())) {
|
||||||
|
if (tgt == null && tgts != null)
|
||||||
|
grp.makeElement("target").setValue(tgts);
|
||||||
|
return grp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Element grp = map.addElement("group");
|
||||||
|
grp.makeElement("source").setValue(srcs);
|
||||||
|
grp.makeElement("target").setValue(tgts);
|
||||||
|
return grp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readPrefix(Map<String, String> prefixes, FHIRLexer lexer) throws FHIRLexerException {
|
||||||
|
String prefix = lexer.take();
|
||||||
|
if (!prefixes.containsKey(prefix))
|
||||||
|
throw lexer.error("Unknown prefix '" + prefix + "'");
|
||||||
|
return prefixes.get(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ConceptMapRelationship readRelationship(FHIRLexer lexer) throws FHIRLexerException {
|
||||||
|
String token = lexer.take();
|
||||||
|
if (token.equals("-"))
|
||||||
|
return ConceptMapRelationship.RELATEDTO;
|
||||||
|
if (token.equals("=="))
|
||||||
|
return ConceptMapRelationship.EQUIVALENT;
|
||||||
|
if (token.equals("!="))
|
||||||
|
return ConceptMapRelationship.NOTRELATEDTO;
|
||||||
|
if (token.equals("<="))
|
||||||
|
return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
|
||||||
|
if (token.equals(">="))
|
||||||
|
return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
|
||||||
|
throw lexer.error("Unknown relationship token '" + token + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseUses(Element result, FHIRLexer lexer) throws FHIRException {
|
||||||
|
lexer.token("uses");
|
||||||
|
Element st = result.addElement("structure");
|
||||||
|
st.makeElement("url").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("url"));
|
||||||
|
if (lexer.hasToken("alias")) {
|
||||||
|
lexer.token("alias");
|
||||||
|
st.makeElement("alias").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
lexer.token("as");
|
||||||
|
st.makeElement("mode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
lexer.skipToken(";");
|
||||||
|
if (lexer.hasComments()) {
|
||||||
|
st.makeElement("documentation").markLocation(lexer.getCommentLocation()).setValue(lexer.getFirstComment());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void parseImports(Element result, FHIRLexer lexer) throws FHIRException {
|
||||||
|
lexer.token("imports");
|
||||||
|
result.addElement("import").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("url"));
|
||||||
|
lexer.skipToken(";");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseGroup(Element result, FHIRLexer lexer) throws FHIRException {
|
||||||
|
SourceLocation commLoc = lexer.getCommentLocation();
|
||||||
|
String comment = lexer.getAllComments();
|
||||||
|
lexer.token("group");
|
||||||
|
Element group = result.addElement("group").markLocation(lexer.getCurrentLocation());
|
||||||
|
if (!Utilities.noString(comment)) {
|
||||||
|
group.makeElement("documentation").markLocation(commLoc).setValue(comment);
|
||||||
|
}
|
||||||
|
boolean newFmt = false;
|
||||||
|
if (lexer.hasToken("for")) {
|
||||||
|
lexer.token("for");
|
||||||
|
SourceLocation loc = lexer.getCurrentLocation();
|
||||||
|
if ("type".equals(lexer.getCurrent())) {
|
||||||
|
lexer.token("type");
|
||||||
|
lexer.token("+");
|
||||||
|
lexer.token("types");
|
||||||
|
group.makeElement("typeMode").markLocation(loc).setValue(StructureMapGroupTypeMode.TYPEANDTYPES.toCode());
|
||||||
|
} else {
|
||||||
|
lexer.token("types");
|
||||||
|
group.makeElement("typeMode").markLocation(loc).setValue(StructureMapGroupTypeMode.TYPES.toCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
if (lexer.hasToken("(")) {
|
||||||
|
newFmt = true;
|
||||||
|
lexer.take();
|
||||||
|
while (!lexer.hasToken(")")) {
|
||||||
|
parseInput(group, lexer, true);
|
||||||
|
if (lexer.hasToken(","))
|
||||||
|
lexer.token(",");
|
||||||
|
}
|
||||||
|
lexer.take();
|
||||||
|
}
|
||||||
|
if (lexer.hasToken("extends")) {
|
||||||
|
lexer.next();
|
||||||
|
group.makeElement("extends").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
if (newFmt) {
|
||||||
|
if (lexer.hasToken("<")) {
|
||||||
|
lexer.token("<");
|
||||||
|
lexer.token("<");
|
||||||
|
if (lexer.hasToken("types")) {
|
||||||
|
group.makeElement("typeMode").markLocation(lexer.getCurrentLocation()).setValue(StructureMapGroupTypeMode.TYPES.toCode());
|
||||||
|
} else {
|
||||||
|
group.makeElement("typeMode").markLocation(lexer.getCurrentLocation()).setValue(StructureMapGroupTypeMode.TYPEANDTYPES.toCode());
|
||||||
|
lexer.token("type");
|
||||||
|
lexer.token("+");
|
||||||
|
}
|
||||||
|
lexer.token(">");
|
||||||
|
lexer.token(">");
|
||||||
|
}
|
||||||
|
lexer.token("{");
|
||||||
|
}
|
||||||
|
if (newFmt) {
|
||||||
|
while (!lexer.hasToken("}")) {
|
||||||
|
if (lexer.done())
|
||||||
|
throw lexer.error("premature termination expecting 'endgroup'");
|
||||||
|
parseRule(result, group, lexer, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (lexer.hasToken("input"))
|
||||||
|
parseInput(group, lexer, false);
|
||||||
|
while (!lexer.hasToken("endgroup")) {
|
||||||
|
if (lexer.done())
|
||||||
|
throw lexer.error("premature termination expecting 'endgroup'");
|
||||||
|
parseRule(result, group, lexer, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lexer.next();
|
||||||
|
if (newFmt && lexer.hasToken(";"))
|
||||||
|
lexer.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void parseRule(Element map, Element context, FHIRLexer lexer, boolean newFmt) throws FHIRException {
|
||||||
|
Element rule = context.addElement("rule").markLocation(lexer.getCurrentLocation());
|
||||||
|
if (!newFmt) {
|
||||||
|
rule.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.takeDottedToken());
|
||||||
|
lexer.token(":");
|
||||||
|
lexer.token("for");
|
||||||
|
} else {
|
||||||
|
if (lexer.hasComments()) {
|
||||||
|
rule.makeElement("documentation").markLocation(lexer.getCommentLocation()).setValue(lexer.getFirstComment());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean done = false;
|
||||||
|
while (!done) {
|
||||||
|
parseSource(rule, lexer);
|
||||||
|
done = !lexer.hasToken(",");
|
||||||
|
if (!done)
|
||||||
|
lexer.next();
|
||||||
|
}
|
||||||
|
if ((newFmt && lexer.hasToken("->")) || (!newFmt && lexer.hasToken("make"))) {
|
||||||
|
lexer.token(newFmt ? "->" : "make");
|
||||||
|
done = false;
|
||||||
|
while (!done) {
|
||||||
|
parseTarget(rule, lexer);
|
||||||
|
done = !lexer.hasToken(",");
|
||||||
|
if (!done)
|
||||||
|
lexer.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lexer.hasToken("then")) {
|
||||||
|
lexer.token("then");
|
||||||
|
if (lexer.hasToken("{")) {
|
||||||
|
lexer.token("{");
|
||||||
|
while (!lexer.hasToken("}")) {
|
||||||
|
if (lexer.done())
|
||||||
|
throw lexer.error("premature termination expecting '}' in nested group");
|
||||||
|
parseRule(map, rule, lexer, newFmt);
|
||||||
|
}
|
||||||
|
lexer.token("}");
|
||||||
|
} else {
|
||||||
|
done = false;
|
||||||
|
while (!done) {
|
||||||
|
parseRuleReference(rule, lexer);
|
||||||
|
done = !lexer.hasToken(",");
|
||||||
|
if (!done)
|
||||||
|
lexer.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!rule.hasChild("documentation") && lexer.hasComments()) {
|
||||||
|
rule.makeElement("documentation").markLocation(lexer.getCommentLocation()).setValue(lexer.getFirstComment());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSimpleSyntax(rule)) {
|
||||||
|
rule.forceElement("source").makeElement("variable").setValue(StructureMapUtilities.AUTO_VAR_NAME);
|
||||||
|
rule.forceElement("target").makeElement("variable").setValue(StructureMapUtilities.AUTO_VAR_NAME);
|
||||||
|
rule.forceElement("target").makeElement("transform").setValue(StructureMapTransform.CREATE.toCode());
|
||||||
|
// no dependencies - imply what is to be done based on types
|
||||||
|
}
|
||||||
|
if (newFmt) {
|
||||||
|
if (lexer.isConstant()) {
|
||||||
|
if (lexer.isStringConstant()) {
|
||||||
|
rule.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("ruleName"));
|
||||||
|
} else {
|
||||||
|
rule.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (rule.getChildrenByName("source").size() != 1 || !rule.getChildrenByName("source").get(0).hasChild("element"))
|
||||||
|
throw lexer.error("Complex rules must have an explicit name");
|
||||||
|
if (rule.getChildrenByName("source").get(0).hasChild("type"))
|
||||||
|
rule.makeElement("name").setValue(rule.getChildrenByName("source").get(0).getNamedChildValue("element") + rule.getChildrenByName("source").get(0).getNamedChildValue("type"));
|
||||||
|
else
|
||||||
|
rule.makeElement("name").setValue(rule.getChildrenByName("source").get(0).getNamedChildValue("element"));
|
||||||
|
}
|
||||||
|
lexer.token(";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseRuleReference(Element rule, FHIRLexer lexer) throws FHIRLexerException {
|
||||||
|
Element ref = rule.addElement("dependent");
|
||||||
|
rule.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
lexer.token("(");
|
||||||
|
boolean done = false;
|
||||||
|
while (!done) {
|
||||||
|
parseParameter(ref, lexer);
|
||||||
|
done = !lexer.hasToken(",");
|
||||||
|
if (!done)
|
||||||
|
lexer.next();
|
||||||
|
}
|
||||||
|
lexer.token(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseSource(Element rule, FHIRLexer lexer) throws FHIRException {
|
||||||
|
Element source = rule.addElement("source").markLocation(lexer.getCurrentLocation());
|
||||||
|
source.makeElement("context").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
if (source.getChildValue("context").equals("search") && lexer.hasToken("(")) {
|
||||||
|
source.makeElement("context").markLocation(lexer.getCurrentLocation()).setValue("@search");
|
||||||
|
lexer.take();
|
||||||
|
SourceLocation loc = lexer.getCurrentLocation();
|
||||||
|
ExpressionNode node = fpe.parse(lexer);
|
||||||
|
source.setUserData(StructureMapUtilities.MAP_SEARCH_EXPRESSION, node);
|
||||||
|
source.makeElement("element").markLocation(loc).setValue(node.toString());
|
||||||
|
lexer.token(")");
|
||||||
|
} else if (lexer.hasToken(".")) {
|
||||||
|
lexer.token(".");
|
||||||
|
source.makeElement("element").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
if (lexer.hasToken(":")) {
|
||||||
|
// type and cardinality
|
||||||
|
lexer.token(":");
|
||||||
|
source.makeElement("type").markLocation(lexer.getCurrentLocation()).setValue(lexer.takeDottedToken());
|
||||||
|
}
|
||||||
|
if (Utilities.isInteger(lexer.getCurrent())) {
|
||||||
|
source.makeElement("min").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
lexer.token("..");
|
||||||
|
source.makeElement("max").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
if (lexer.hasToken("default")) {
|
||||||
|
lexer.token("default");
|
||||||
|
source.makeElement("defaultValue").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("default value"));
|
||||||
|
}
|
||||||
|
if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one")) {
|
||||||
|
source.makeElement("listMode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lexer.hasToken("as")) {
|
||||||
|
lexer.take();
|
||||||
|
source.makeElement("variable").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
if (lexer.hasToken("where")) {
|
||||||
|
lexer.take();
|
||||||
|
SourceLocation loc = lexer.getCurrentLocation();
|
||||||
|
ExpressionNode node = fpe.parse(lexer);
|
||||||
|
source.setUserData(StructureMapUtilities.MAP_WHERE_EXPRESSION, node);
|
||||||
|
source.makeElement("condition").markLocation(loc).setValue(node.toString());
|
||||||
|
}
|
||||||
|
if (lexer.hasToken("check")) {
|
||||||
|
lexer.take();
|
||||||
|
SourceLocation loc = lexer.getCurrentLocation();
|
||||||
|
ExpressionNode node = fpe.parse(lexer);
|
||||||
|
source.setUserData(StructureMapUtilities.MAP_WHERE_CHECK, node);
|
||||||
|
source.makeElement("check").markLocation(loc).setValue(node.toString());
|
||||||
|
}
|
||||||
|
if (lexer.hasToken("log")) {
|
||||||
|
lexer.take();
|
||||||
|
SourceLocation loc = lexer.getCurrentLocation();
|
||||||
|
ExpressionNode node = fpe.parse(lexer);
|
||||||
|
source.setUserData(StructureMapUtilities.MAP_WHERE_CHECK, node);
|
||||||
|
source.makeElement("logMessage").markLocation(loc).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseTarget(Element rule, FHIRLexer lexer) throws FHIRException {
|
||||||
|
Element target = rule.addElement("target").markLocation(lexer.getCurrentLocation());
|
||||||
|
SourceLocation loc = lexer.getCurrentLocation();
|
||||||
|
String start = lexer.take();
|
||||||
|
if (lexer.hasToken(".")) {
|
||||||
|
target.makeElement("context").markLocation(loc).setValue(start);
|
||||||
|
start = null;
|
||||||
|
lexer.token(".");
|
||||||
|
target.makeElement("element").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
String name;
|
||||||
|
boolean isConstant = false;
|
||||||
|
if (lexer.hasToken("=")) {
|
||||||
|
if (start != null) {
|
||||||
|
target.makeElement("context").markLocation(loc).setValue(start);
|
||||||
|
}
|
||||||
|
lexer.token("=");
|
||||||
|
isConstant = lexer.isConstant();
|
||||||
|
loc = lexer.getCurrentLocation();
|
||||||
|
name = lexer.take();
|
||||||
|
} else {
|
||||||
|
loc = lexer.getCurrentLocation();
|
||||||
|
name = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("(".equals(name)) {
|
||||||
|
// inline fluentpath expression
|
||||||
|
target.makeElement("transform").markLocation(lexer.getCurrentLocation()).setValue(StructureMapTransform.EVALUATE.toCode());
|
||||||
|
loc = lexer.getCurrentLocation();
|
||||||
|
ExpressionNode node = fpe.parse(lexer);
|
||||||
|
target.setUserData(StructureMapUtilities.MAP_EXPRESSION, node);
|
||||||
|
target.addElement("parameter").markLocation(loc).setValue(node.toString());
|
||||||
|
lexer.token(")");
|
||||||
|
} else if (lexer.hasToken("(")) {
|
||||||
|
target.makeElement("transform").markLocation(loc).setValue(name);
|
||||||
|
lexer.token("(");
|
||||||
|
if (target.getChildValue("transform").equals(StructureMapTransform.EVALUATE.toCode())) {
|
||||||
|
parseParameter(target, lexer);
|
||||||
|
lexer.token(",");
|
||||||
|
loc = lexer.getCurrentLocation();
|
||||||
|
ExpressionNode node = fpe.parse(lexer);
|
||||||
|
target.setUserData(StructureMapUtilities.MAP_EXPRESSION, node);
|
||||||
|
target.addElement("parameter").markLocation(loc).setValue(node.toString());
|
||||||
|
} else {
|
||||||
|
while (!lexer.hasToken(")")) {
|
||||||
|
parseParameter(target, lexer);
|
||||||
|
if (!lexer.hasToken(")"))
|
||||||
|
lexer.token(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lexer.token(")");
|
||||||
|
} else if (name != null) {
|
||||||
|
target.makeElement("transform").markLocation(loc).setValue(StructureMapTransform.COPY.toCode());
|
||||||
|
if (!isConstant) {
|
||||||
|
loc = lexer.getCurrentLocation();
|
||||||
|
String id = name;
|
||||||
|
while (lexer.hasToken(".")) {
|
||||||
|
id = id + lexer.take() + lexer.take();
|
||||||
|
}
|
||||||
|
target.addElement("parameter").markLocation(loc).setValue(id);
|
||||||
|
} else {
|
||||||
|
target.addElement("parameter").markLocation(lexer.getCurrentLocation()).setValue(readConstant(name, lexer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lexer.hasToken("as")) {
|
||||||
|
lexer.take();
|
||||||
|
target.makeElement("variable").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) {
|
||||||
|
if (lexer.getCurrent().equals("share")) {
|
||||||
|
target.makeElement("listMode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
target.makeElement("listRuleId").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
} else {
|
||||||
|
target.makeElement("listMode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseParameter(Element ref, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError {
|
||||||
|
if (!lexer.isConstant()) {
|
||||||
|
ref.addElement("parameter").markLocation(lexer.getCurrentLocation()).setType("string").setValue(lexer.take());
|
||||||
|
} else if (lexer.isStringConstant())
|
||||||
|
ref.addElement("parameter").markLocation(lexer.getCurrentLocation()).setType("string").setValue(lexer.readConstant("??"));
|
||||||
|
else {
|
||||||
|
ref.addElement("parameter").markLocation(lexer.getCurrentLocation()).setType("string").setValue(readConstant(lexer.take(), lexer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseInput(Element group, FHIRLexer lexer, boolean newFmt) throws FHIRException {
|
||||||
|
Element input = group.addElement("input");
|
||||||
|
if (newFmt) {
|
||||||
|
input.makeElement("mode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
} else
|
||||||
|
lexer.token("input");
|
||||||
|
input.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
if (lexer.hasToken(":")) {
|
||||||
|
lexer.token(":");
|
||||||
|
input.makeElement("type").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
}
|
||||||
|
if (!newFmt) {
|
||||||
|
lexer.token("as");
|
||||||
|
input.makeElement("mode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
||||||
|
if (lexer.hasComments()) {
|
||||||
|
input.makeElement("documentation").markLocation(lexer.getCommentLocation()).setValue(lexer.getFirstComment());
|
||||||
|
}
|
||||||
|
lexer.skipToken(";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSimpleSyntax(Element rule) {
|
||||||
|
return
|
||||||
|
(rule.getChildren("source").size() == 1 && rule.getChildren("source").get(0).hasChild("context") && rule.getChildren("source").get(0).hasChild("element") && !rule.getChildren("source").get(0).hasChild("variable")) &&
|
||||||
|
(rule.getChildren("target").size() == 1 && rule.getChildren("target").get(0).hasChild("context") && rule.getChildren("target").get(0).hasChild("element") && !rule.getChildren("target").get(0).hasChild("variable") &&
|
||||||
|
!rule.getChildren("target").get(0).hasChild("parameter")) &&
|
||||||
|
(rule.getChildren("dependent").size() == 0 && rule.getChildren("rule").size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readConstant(String s, FHIRLexer lexer) throws FHIRLexerException {
|
||||||
|
if (Utilities.isInteger(s))
|
||||||
|
return s;
|
||||||
|
else if (Utilities.isDecimal(s, false))
|
||||||
|
return s;
|
||||||
|
else if (Utilities.existsInList(s, "true", "false"))
|
||||||
|
return s;
|
||||||
|
else
|
||||||
|
return lexer.processConstant(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ import org.hl7.fhir.r5.model.StructureDefinition;
|
||||||
public class Manager {
|
public class Manager {
|
||||||
|
|
||||||
//TODO use EnumMap
|
//TODO use EnumMap
|
||||||
public enum FhirFormat { XML, JSON, TURTLE, TEXT, VBAR, SHC;
|
public enum FhirFormat { XML, JSON, TURTLE, TEXT, VBAR, SHC, FML;
|
||||||
// SHC = smart health cards, including as text versions of QR codes
|
// SHC = smart health cards, including as text versions of QR codes
|
||||||
|
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
|
@ -65,6 +65,8 @@ public class Manager {
|
||||||
return "hl7";
|
return "hl7";
|
||||||
case SHC:
|
case SHC:
|
||||||
return "shc";
|
return "shc";
|
||||||
|
case FML:
|
||||||
|
return "fml";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -81,6 +83,8 @@ public class Manager {
|
||||||
return TEXT;
|
return TEXT;
|
||||||
case "hl7":
|
case "hl7":
|
||||||
return VBAR;
|
return VBAR;
|
||||||
|
case "fml":
|
||||||
|
return FML;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -115,6 +119,7 @@ public class Manager {
|
||||||
case TURTLE : return new TurtleParser(context);
|
case TURTLE : return new TurtleParser(context);
|
||||||
case VBAR : return new VerticalBarParser(context);
|
case VBAR : return new VerticalBarParser(context);
|
||||||
case SHC : return new SHCParser(context);
|
case SHC : return new SHCParser(context);
|
||||||
|
case FML : return new FmlParser(context);
|
||||||
case TEXT : throw new Error("Programming logic error: do not call makeParser for a text resource");
|
case TEXT : throw new Error("Programming logic error: do not call makeParser for a text resource");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -123,6 +128,7 @@ public class Manager {
|
||||||
public static Element build(IWorkerContext context, StructureDefinition sd) {
|
public static Element build(IWorkerContext context, StructureDefinition sd) {
|
||||||
Property p = new Property(context, sd.getSnapshot().getElementFirstRep(), sd);
|
Property p = new Property(context, sd.getSnapshot().getElementFirstRep(), sd);
|
||||||
Element e = new Element(p.getName(), p);
|
Element e = new Element(p.getName(), p);
|
||||||
|
e.setPath(sd.getType());
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -270,8 +270,9 @@ public class Property {
|
||||||
String tc = definition.getType().get(0).getCode();
|
String tc = definition.getType().get(0).getCode();
|
||||||
return definition.getType().size() == 1 && (("Resource".equals(tc) || "DomainResource".equals(tc)) || Utilities.existsInList(tc, context.getResourceNames()));
|
return definition.getType().size() == 1 && (("Resource".equals(tc) || "DomainResource".equals(tc)) || Utilities.existsInList(tc, context.getResourceNames()));
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
return !definition.getPath().contains(".") && (structure.getKind() == StructureDefinitionKind.RESOURCE);
|
return !definition.getPath().contains(".") && (structure.getKind() == StructureDefinitionKind.RESOURCE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isList() {
|
public boolean isList() {
|
||||||
|
|
|
@ -13521,6 +13521,9 @@ If a pattern[x] is declared on a repeating element, the pattern applies to all r
|
||||||
return hasPath() ? getPath().contains(".") ? getPath().substring(getPath().lastIndexOf(".")+1) : getPath() : null;
|
return hasPath() ? getPath().contains(".") ? getPath().substring(getPath().lastIndexOf(".")+1) : getPath() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNameBase() {
|
||||||
|
return getName().replace("[x]", "");
|
||||||
|
}
|
||||||
public boolean unbounded() {
|
public boolean unbounded() {
|
||||||
return getMax().equals("*") || Integer.parseInt(getMax()) > 1;
|
return getMax().equals("*") || Integer.parseInt(getMax()) > 1;
|
||||||
}
|
}
|
||||||
|
@ -13554,6 +13557,8 @@ If a pattern[x] is declared on a repeating element, the pattern applies to all r
|
||||||
return getMin() == 1;
|
return getMin() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// end addition
|
// end addition
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5983,7 +5983,7 @@ public class FHIRPathEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, ExpressionNode expr) throws PathEngineException {
|
public ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, ExpressionNode expr) throws PathEngineException {
|
||||||
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
|
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
|
||||||
if (ed.getPath().equals(path)) {
|
if (ed.getPath().equals(path)) {
|
||||||
if (ed.hasContentReference()) {
|
if (ed.hasContentReference()) {
|
||||||
|
|
|
@ -103,7 +103,7 @@ public class StructureMapUtilities {
|
||||||
public static final String MAP_SEARCH_EXPRESSION = "map.search.expression";
|
public static final String MAP_SEARCH_EXPRESSION = "map.search.expression";
|
||||||
public static final String MAP_EXPRESSION = "map.transform.expression";
|
public static final String MAP_EXPRESSION = "map.transform.expression";
|
||||||
private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true;
|
private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true;
|
||||||
private static final String AUTO_VAR_NAME = "vvv";
|
public static final String AUTO_VAR_NAME = "vvv";
|
||||||
|
|
||||||
private final IWorkerContext worker;
|
private final IWorkerContext worker;
|
||||||
private final FHIRPathEngine fpe;
|
private final FHIRPathEngine fpe;
|
||||||
|
@ -628,6 +628,7 @@ public class StructureMapUtilities {
|
||||||
lexer.token("=");
|
lexer.token("=");
|
||||||
result.setName(lexer.readConstant("name"));
|
result.setName(lexer.readConstant("name"));
|
||||||
result.setDescription(lexer.getAllComments());
|
result.setDescription(lexer.getAllComments());
|
||||||
|
result.setStatus(PublicationStatus.DRAFT);
|
||||||
while (lexer.hasToken("conceptmap"))
|
while (lexer.hasToken("conceptmap"))
|
||||||
parseConceptMap(result, lexer);
|
parseConceptMap(result, lexer);
|
||||||
|
|
||||||
|
@ -643,37 +644,7 @@ public class StructureMapUtilities {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Element parseEM(String text, String srcName, List<ValidationMessage> list) throws FHIRException {
|
|
||||||
FHIRLexer lexer = new FHIRLexer(text, srcName);
|
|
||||||
if (lexer.done())
|
|
||||||
throw lexer.error("Map Input cannot be empty");
|
|
||||||
lexer.token("map");
|
|
||||||
Element result = Manager.build(worker, worker.fetchTypeDefinition("StructureMap"));
|
|
||||||
try {
|
|
||||||
result.makeElement("url").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("url"));
|
|
||||||
lexer.token("=");
|
|
||||||
result.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("name"));
|
|
||||||
result.makeElement("description").markLocation(lexer.getCurrentLocation()).setValue(lexer.getAllComments());
|
|
||||||
while (lexer.hasToken("conceptmap"))
|
|
||||||
parseConceptMapEM(result, lexer);
|
|
||||||
|
|
||||||
while (lexer.hasToken("uses"))
|
|
||||||
parseUsesEM(result, lexer);
|
|
||||||
while (lexer.hasToken("imports"))
|
|
||||||
parseImportsEM(result, lexer);
|
|
||||||
|
|
||||||
while (!lexer.done()) {
|
|
||||||
parseGroupEM(result, lexer);
|
|
||||||
}
|
|
||||||
} catch (FHIRLexerException e) {
|
|
||||||
list.add(new ValidationMessage(Source.InstanceValidator, IssueType.INVALID, e.getLocation().getLine(), e.getLocation().getColumn(), null, e.getMessage(), IssueSeverity.FATAL));
|
|
||||||
} catch (Exception e) {
|
|
||||||
list.add(new ValidationMessage(Source.InstanceValidator, IssueType.INVALID, null, e.getMessage(), IssueSeverity.FATAL));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException {
|
private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException {
|
||||||
lexer.token("conceptmap");
|
lexer.token("conceptmap");
|
||||||
ConceptMap map = new ConceptMap();
|
ConceptMap map = new ConceptMap();
|
||||||
|
@ -730,63 +701,7 @@ public class StructureMapUtilities {
|
||||||
lexer.token("}");
|
lexer.token("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseConceptMapEM(Element result, FHIRLexer lexer) throws FHIRLexerException {
|
|
||||||
lexer.token("conceptmap");
|
|
||||||
Element map = Manager.build(worker, worker.fetchTypeDefinition("ConceptMap"));
|
|
||||||
Element eid = map.makeElement("id").markLocation(lexer.getCurrentLocation());
|
|
||||||
String id = lexer.readConstant("map id");
|
|
||||||
if (id.startsWith("#"))
|
|
||||||
throw lexer.error("Concept Map identifier must start with #");
|
|
||||||
eid.setValue(id);
|
|
||||||
map.makeElement("status").setValue(PublicationStatus.DRAFT.toCode()); // todo: how to add this to the text format
|
|
||||||
result.makeElement("contained").setElement("resource", map);
|
|
||||||
lexer.token("{");
|
|
||||||
// lexer.token("source");
|
|
||||||
// map.setSource(new UriType(lexer.readConstant("source")));
|
|
||||||
// lexer.token("target");
|
|
||||||
// map.setSource(new UriType(lexer.readConstant("target")));
|
|
||||||
Map<String, String> prefixes = new HashMap<String, String>();
|
|
||||||
while (lexer.hasToken("prefix")) {
|
|
||||||
lexer.token("prefix");
|
|
||||||
String n = lexer.take();
|
|
||||||
lexer.token("=");
|
|
||||||
String v = lexer.readConstant("prefix url");
|
|
||||||
prefixes.put(n, v);
|
|
||||||
}
|
|
||||||
while (lexer.hasToken("unmapped")) {
|
|
||||||
lexer.token("unmapped");
|
|
||||||
lexer.token("for");
|
|
||||||
String n = readPrefix(prefixes, lexer);
|
|
||||||
Element g = getGroupE(map, n, null);
|
|
||||||
lexer.token("=");
|
|
||||||
SourceLocation loc = lexer.getCurrentLocation();
|
|
||||||
String v = lexer.take();
|
|
||||||
if (v.equals("provided")) {
|
|
||||||
g.makeElement("unmapped").makeElement("mode").markLocation(loc).setValue(ConceptMapGroupUnmappedMode.USESOURCECODE.toCode());
|
|
||||||
} else
|
|
||||||
throw lexer.error("Only unmapped mode PROVIDED is supported at this time");
|
|
||||||
}
|
|
||||||
while (!lexer.hasToken("}")) {
|
|
||||||
String srcs = readPrefix(prefixes, lexer);
|
|
||||||
lexer.token(":");
|
|
||||||
SourceLocation scloc = lexer.getCurrentLocation();
|
|
||||||
String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take();
|
|
||||||
SourceLocation relLoc = lexer.getCurrentLocation();
|
|
||||||
ConceptMapRelationship rel = readRelationship(lexer);
|
|
||||||
String tgts = readPrefix(prefixes, lexer);
|
|
||||||
Element g = getGroupE(map, srcs, tgts);
|
|
||||||
Element e = g.addElement("element");
|
|
||||||
e.makeElement("code").markLocation(scloc).setValue(sc.startsWith("\"") ? lexer.processConstant(sc) : sc);
|
|
||||||
Element tgt = e.addElement("target");
|
|
||||||
tgt.makeElement("relationship").markLocation(relLoc).setValue(rel.toCode());
|
|
||||||
lexer.token(":");
|
|
||||||
tgt.makeElement("code").markLocation(lexer.getCurrentLocation()).setValue(lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take());
|
|
||||||
if (lexer.hasComments()) {
|
|
||||||
tgt.makeElement("comment").markLocation(lexer.getCommentLocation()).setValue(lexer.getFirstComment());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lexer.token("}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) {
|
private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) {
|
||||||
for (ConceptMapGroupComponent grp : map.getGroup()) {
|
for (ConceptMapGroupComponent grp : map.getGroup()) {
|
||||||
|
@ -803,23 +718,7 @@ public class StructureMapUtilities {
|
||||||
return grp;
|
return grp;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Element getGroupE(Element map, String srcs, String tgts) {
|
|
||||||
for (Element grp : map.getChildrenByName("group")) {
|
|
||||||
if (grp.getChildValue("source").equals(srcs)) {
|
|
||||||
Element tgt = grp.getNamedChild("target");
|
|
||||||
if (tgt == null || tgts == null || tgts.equals(tgt.getValue())) {
|
|
||||||
if (tgt == null && tgts != null)
|
|
||||||
grp.makeElement("target").setValue(tgts);
|
|
||||||
return grp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element grp = map.addElement("group");
|
|
||||||
grp.makeElement("source").setValue(srcs);
|
|
||||||
grp.makeElement("target").setValue(tgts);
|
|
||||||
return grp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String readPrefix(Map<String, String> prefixes, FHIRLexer lexer) throws FHIRLexerException {
|
private String readPrefix(Map<String, String> prefixes, FHIRLexer lexer) throws FHIRLexerException {
|
||||||
String prefix = lexer.take();
|
String prefix = lexer.take();
|
||||||
|
@ -859,21 +758,7 @@ public class StructureMapUtilities {
|
||||||
st.setDocumentation(lexer.getFirstComment());
|
st.setDocumentation(lexer.getFirstComment());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseUsesEM(Element result, FHIRLexer lexer) throws FHIRException {
|
|
||||||
lexer.token("uses");
|
|
||||||
Element st = result.addElement("structure");
|
|
||||||
st.makeElement("url").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("url"));
|
|
||||||
if (lexer.hasToken("alias")) {
|
|
||||||
lexer.token("alias");
|
|
||||||
st.makeElement("alias").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
lexer.token("as");
|
|
||||||
st.makeElement("mode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
lexer.skipToken(";");
|
|
||||||
if (lexer.hasComments()) {
|
|
||||||
st.makeElement("documentation").markLocation(lexer.getCommentLocation()).setValue(lexer.getFirstComment());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException {
|
private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException {
|
||||||
lexer.token("imports");
|
lexer.token("imports");
|
||||||
|
@ -881,12 +766,6 @@ public class StructureMapUtilities {
|
||||||
lexer.skipToken(";");
|
lexer.skipToken(";");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseImportsEM(Element result, FHIRLexer lexer) throws FHIRException {
|
|
||||||
lexer.token("imports");
|
|
||||||
result.addElement("import").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("url"));
|
|
||||||
lexer.skipToken(";");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException {
|
private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException {
|
||||||
String comment = lexer.getAllComments();
|
String comment = lexer.getAllComments();
|
||||||
lexer.token("group");
|
lexer.token("group");
|
||||||
|
@ -959,78 +838,7 @@ public class StructureMapUtilities {
|
||||||
lexer.next();
|
lexer.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseGroupEM(Element result, FHIRLexer lexer) throws FHIRException {
|
|
||||||
SourceLocation commLoc = lexer.getCommentLocation();
|
|
||||||
String comment = lexer.getAllComments();
|
|
||||||
lexer.token("group");
|
|
||||||
Element group = result.addElement("group");
|
|
||||||
if (comment != null) {
|
|
||||||
group.makeElement("documentation").markLocation(commLoc).setValue(comment);
|
|
||||||
}
|
|
||||||
boolean newFmt = false;
|
|
||||||
if (lexer.hasToken("for")) {
|
|
||||||
lexer.token("for");
|
|
||||||
SourceLocation loc = lexer.getCurrentLocation();
|
|
||||||
if ("type".equals(lexer.getCurrent())) {
|
|
||||||
lexer.token("type");
|
|
||||||
lexer.token("+");
|
|
||||||
lexer.token("types");
|
|
||||||
group.makeElement("typeMode").markLocation(loc).setValue(StructureMapGroupTypeMode.TYPEANDTYPES.toCode());
|
|
||||||
} else {
|
|
||||||
lexer.token("types");
|
|
||||||
group.makeElement("typeMode").markLocation(loc).setValue(StructureMapGroupTypeMode.TYPES.toCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
group.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
if (lexer.hasToken("(")) {
|
|
||||||
newFmt = true;
|
|
||||||
lexer.take();
|
|
||||||
while (!lexer.hasToken(")")) {
|
|
||||||
parseInputEM(group, lexer, true);
|
|
||||||
if (lexer.hasToken(","))
|
|
||||||
lexer.token(",");
|
|
||||||
}
|
|
||||||
lexer.take();
|
|
||||||
}
|
|
||||||
if (lexer.hasToken("extends")) {
|
|
||||||
lexer.next();
|
|
||||||
group.makeElement("extends").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
if (newFmt) {
|
|
||||||
if (lexer.hasToken("<")) {
|
|
||||||
lexer.token("<");
|
|
||||||
lexer.token("<");
|
|
||||||
if (lexer.hasToken("types")) {
|
|
||||||
group.makeElement("typeMode").markLocation(lexer.getCurrentLocation()).setValue(StructureMapGroupTypeMode.TYPES.toCode());
|
|
||||||
} else {
|
|
||||||
group.makeElement("typeMode").markLocation(lexer.getCurrentLocation()).setValue(StructureMapGroupTypeMode.TYPEANDTYPES.toCode());
|
|
||||||
lexer.token("type");
|
|
||||||
lexer.token("+");
|
|
||||||
}
|
|
||||||
lexer.token(">");
|
|
||||||
lexer.token(">");
|
|
||||||
}
|
|
||||||
lexer.token("{");
|
|
||||||
}
|
|
||||||
if (newFmt) {
|
|
||||||
while (!lexer.hasToken("}")) {
|
|
||||||
if (lexer.done())
|
|
||||||
throw lexer.error("premature termination expecting 'endgroup'");
|
|
||||||
parseRuleEM(result, group, lexer, true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while (lexer.hasToken("input"))
|
|
||||||
parseInputEM(group, lexer, false);
|
|
||||||
while (!lexer.hasToken("endgroup")) {
|
|
||||||
if (lexer.done())
|
|
||||||
throw lexer.error("premature termination expecting 'endgroup'");
|
|
||||||
parseRuleEM(result, group, lexer, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lexer.next();
|
|
||||||
if (newFmt && lexer.hasToken(";"))
|
|
||||||
lexer.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer, boolean newFmt) throws FHIRException {
|
private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer, boolean newFmt) throws FHIRException {
|
||||||
StructureMapGroupInputComponent input = group.addInput();
|
StructureMapGroupInputComponent input = group.addInput();
|
||||||
|
@ -1051,26 +859,7 @@ public class StructureMapUtilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseInputEM(Element group, FHIRLexer lexer, boolean newFmt) throws FHIRException {
|
|
||||||
Element input = group.addElement("input");
|
|
||||||
if (newFmt) {
|
|
||||||
input.makeElement("mode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
} else
|
|
||||||
lexer.token("input");
|
|
||||||
input.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
if (lexer.hasToken(":")) {
|
|
||||||
lexer.token(":");
|
|
||||||
input.makeElement("type").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
if (!newFmt) {
|
|
||||||
lexer.token("as");
|
|
||||||
input.makeElement("mode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
if (lexer.hasComments()) {
|
|
||||||
input.makeElement("documentation").markLocation(lexer.getCommentLocation()).setValue(lexer.getFirstComment());
|
|
||||||
}
|
|
||||||
lexer.skipToken(";");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseRule(StructureMap map, List<StructureMapGroupRuleComponent> list, FHIRLexer lexer, boolean newFmt) throws FHIRException {
|
private void parseRule(StructureMap map, List<StructureMapGroupRuleComponent> list, FHIRLexer lexer, boolean newFmt) throws FHIRException {
|
||||||
StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent();
|
StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent();
|
||||||
|
@ -1147,84 +936,6 @@ public class StructureMapUtilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseRuleEM(Element map, Element context, FHIRLexer lexer, boolean newFmt) throws FHIRException {
|
|
||||||
Element rule = context.addElement("rule");
|
|
||||||
if (!newFmt) {
|
|
||||||
rule.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.takeDottedToken());
|
|
||||||
lexer.token(":");
|
|
||||||
lexer.token("for");
|
|
||||||
} else {
|
|
||||||
if (lexer.hasComments()) {
|
|
||||||
rule.makeElement("documentation").markLocation(lexer.getCommentLocation()).setValue(lexer.getFirstComment());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean done = false;
|
|
||||||
while (!done) {
|
|
||||||
parseSourceEM(rule, lexer);
|
|
||||||
done = !lexer.hasToken(",");
|
|
||||||
if (!done)
|
|
||||||
lexer.next();
|
|
||||||
}
|
|
||||||
if ((newFmt && lexer.hasToken("->")) || (!newFmt && lexer.hasToken("make"))) {
|
|
||||||
lexer.token(newFmt ? "->" : "make");
|
|
||||||
done = false;
|
|
||||||
while (!done) {
|
|
||||||
parseTargetEM(rule, lexer);
|
|
||||||
done = !lexer.hasToken(",");
|
|
||||||
if (!done)
|
|
||||||
lexer.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lexer.hasToken("then")) {
|
|
||||||
lexer.token("then");
|
|
||||||
if (lexer.hasToken("{")) {
|
|
||||||
lexer.token("{");
|
|
||||||
while (!lexer.hasToken("}")) {
|
|
||||||
if (lexer.done())
|
|
||||||
throw lexer.error("premature termination expecting '}' in nested group");
|
|
||||||
parseRuleEM(map, rule, lexer, newFmt);
|
|
||||||
}
|
|
||||||
lexer.token("}");
|
|
||||||
} else {
|
|
||||||
done = false;
|
|
||||||
while (!done) {
|
|
||||||
parseRuleReferenceEM(rule, lexer);
|
|
||||||
done = !lexer.hasToken(",");
|
|
||||||
if (!done)
|
|
||||||
lexer.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!rule.hasChild("documentation") && lexer.hasComments()) {
|
|
||||||
rule.makeElement("documentation").markLocation(lexer.getCommentLocation()).setValue(lexer.getFirstComment());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSimpleSyntaxEM(rule)) {
|
|
||||||
rule.forceElement("source").makeElement("variable").setValue(AUTO_VAR_NAME);
|
|
||||||
rule.forceElement("target").makeElement("variable").setValue(AUTO_VAR_NAME);
|
|
||||||
rule.forceElement("target").makeElement("transform").setValue(StructureMapTransform.CREATE.toCode());
|
|
||||||
// no dependencies - imply what is to be done based on types
|
|
||||||
}
|
|
||||||
if (newFmt) {
|
|
||||||
if (lexer.isConstant()) {
|
|
||||||
if (lexer.isStringConstant()) {
|
|
||||||
rule.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("ruleName"));
|
|
||||||
} else {
|
|
||||||
rule.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (rule.getChildrenByName("source").size() != 1 || !rule.getChildrenByName("source").get(0).hasChild("element"))
|
|
||||||
throw lexer.error("Complex rules must have an explicit name");
|
|
||||||
if (rule.getChildrenByName("source").get(0).hasChild("type"))
|
|
||||||
rule.makeElement("name").setValue(rule.getChildrenByName("source").get(0).getNamedChildValue("element") + "-" + rule.getChildrenByName("source").get(0).getNamedChildValue("type"));
|
|
||||||
else
|
|
||||||
rule.makeElement("name").setValue(rule.getChildrenByName("source").get(0).getNamedChildValue("element"));
|
|
||||||
}
|
|
||||||
lexer.token(";");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) {
|
private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) {
|
||||||
return
|
return
|
||||||
(rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) &&
|
(rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) &&
|
||||||
|
@ -1233,14 +944,7 @@ public class StructureMapUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean isSimpleSyntaxEM(Element rule) {
|
|
||||||
return
|
|
||||||
(rule.getChildren("source").size() == 1 && rule.getChildren("source").get(0).hasChild("context") && rule.getChildren("source").get(0).hasChild("element") && !rule.getChildren("source").get(0).hasChild("variable")) &&
|
|
||||||
(rule.getChildren("target").size() == 1 && rule.getChildren("target").get(0).hasChild("context") && rule.getChildren("target").get(0).hasChild("element") && !rule.getChildren("target").get(0).hasChild("variable") &&
|
|
||||||
!rule.getChildren("target").get(0).hasChild("parameter")) &&
|
|
||||||
(rule.getChildren("dependent").size() == 0 && rule.getChildren("rule").size() == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException {
|
private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException {
|
||||||
StructureMapGroupRuleDependentComponent ref = rule.addDependent();
|
StructureMapGroupRuleDependentComponent ref = rule.addDependent();
|
||||||
ref.setName(lexer.take());
|
ref.setName(lexer.take());
|
||||||
|
@ -1255,20 +959,7 @@ public class StructureMapUtilities {
|
||||||
lexer.token(")");
|
lexer.token(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseRuleReferenceEM(Element rule, FHIRLexer lexer) throws FHIRLexerException {
|
|
||||||
Element ref = rule.addElement("dependent");
|
|
||||||
rule.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
lexer.token("(");
|
|
||||||
boolean done = false;
|
|
||||||
while (!done) {
|
|
||||||
parseParameterEM(ref, lexer);
|
|
||||||
done = !lexer.hasToken(",");
|
|
||||||
if (!done)
|
|
||||||
lexer.next();
|
|
||||||
}
|
|
||||||
lexer.token(")");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
|
private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
|
||||||
StructureMapGroupRuleSourceComponent source = rule.addSource();
|
StructureMapGroupRuleSourceComponent source = rule.addSource();
|
||||||
source.setContext(lexer.take());
|
source.setContext(lexer.take());
|
||||||
|
@ -1324,66 +1015,7 @@ public class StructureMapUtilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseSourceEM(Element rule, FHIRLexer lexer) throws FHIRException {
|
|
||||||
Element source = rule.addElement("source");
|
|
||||||
source.makeElement("context").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
if (source.getChildValue("context").equals("search") && lexer.hasToken("(")) {
|
|
||||||
source.makeElement("context").markLocation(lexer.getCurrentLocation()).setValue("@search");
|
|
||||||
lexer.take();
|
|
||||||
SourceLocation loc = lexer.getCurrentLocation();
|
|
||||||
ExpressionNode node = fpe.parse(lexer);
|
|
||||||
source.setUserData(MAP_SEARCH_EXPRESSION, node);
|
|
||||||
source.makeElement("element").markLocation(loc).setValue(node.toString());
|
|
||||||
lexer.token(")");
|
|
||||||
} else if (lexer.hasToken(".")) {
|
|
||||||
lexer.token(".");
|
|
||||||
source.makeElement("element").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
if (lexer.hasToken(":")) {
|
|
||||||
// type and cardinality
|
|
||||||
lexer.token(":");
|
|
||||||
source.setType(lexer.takeDottedToken());
|
|
||||||
if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) {
|
|
||||||
source.makeElement("min").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
lexer.token("..");
|
|
||||||
source.makeElement("max").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lexer.hasToken("default")) {
|
|
||||||
lexer.token("default");
|
|
||||||
source.makeElement("defaultValue").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("default value"));
|
|
||||||
}
|
|
||||||
if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one")) {
|
|
||||||
source.makeElement("listMode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lexer.hasToken("as")) {
|
|
||||||
lexer.take();
|
|
||||||
source.makeElement("variable").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
if (lexer.hasToken("where")) {
|
|
||||||
lexer.take();
|
|
||||||
SourceLocation loc = lexer.getCurrentLocation();
|
|
||||||
ExpressionNode node = fpe.parse(lexer);
|
|
||||||
source.setUserData(MAP_WHERE_EXPRESSION, node);
|
|
||||||
source.makeElement("condition").markLocation(loc).setValue(node.toString());
|
|
||||||
}
|
|
||||||
if (lexer.hasToken("check")) {
|
|
||||||
lexer.take();
|
|
||||||
SourceLocation loc = lexer.getCurrentLocation();
|
|
||||||
ExpressionNode node = fpe.parse(lexer);
|
|
||||||
source.setUserData(MAP_WHERE_CHECK, node);
|
|
||||||
source.makeElement("check").markLocation(loc).setValue(node.toString());
|
|
||||||
}
|
|
||||||
if (lexer.hasToken("log")) {
|
|
||||||
lexer.take();
|
|
||||||
SourceLocation loc = lexer.getCurrentLocation();
|
|
||||||
ExpressionNode node = fpe.parse(lexer);
|
|
||||||
source.setUserData(MAP_WHERE_CHECK, node);
|
|
||||||
source.makeElement("logMessage").markLocation(loc).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
|
private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
|
||||||
StructureMapGroupRuleTargetComponent target = rule.addTarget();
|
StructureMapGroupRuleTargetComponent target = rule.addTarget();
|
||||||
String start = lexer.take();
|
String start = lexer.take();
|
||||||
|
@ -1458,85 +1090,7 @@ public class StructureMapUtilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseTargetEM(Element rule, FHIRLexer lexer) throws FHIRException {
|
|
||||||
Element target = rule.addElement("target");
|
|
||||||
SourceLocation loc = lexer.getCurrentLocation();
|
|
||||||
String start = lexer.take();
|
|
||||||
if (lexer.hasToken(".")) {
|
|
||||||
target.makeElement("context").markLocation(loc).setValue(start);
|
|
||||||
start = null;
|
|
||||||
lexer.token(".");
|
|
||||||
target.makeElement("element").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
String name;
|
|
||||||
boolean isConstant = false;
|
|
||||||
if (lexer.hasToken("=")) {
|
|
||||||
if (start != null) {
|
|
||||||
target.makeElement("context").markLocation(loc).setValue(start);
|
|
||||||
}
|
|
||||||
lexer.token("=");
|
|
||||||
isConstant = lexer.isConstant();
|
|
||||||
loc = lexer.getCurrentLocation();
|
|
||||||
name = lexer.take();
|
|
||||||
} else {
|
|
||||||
loc = lexer.getCurrentLocation();
|
|
||||||
name = start;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("(".equals(name)) {
|
|
||||||
// inline fluentpath expression
|
|
||||||
target.makeElement("transform").markLocation(lexer.getCurrentLocation()).setValue(StructureMapTransform.EVALUATE.toCode());
|
|
||||||
loc = lexer.getCurrentLocation();
|
|
||||||
ExpressionNode node = fpe.parse(lexer);
|
|
||||||
target.setUserData(MAP_EXPRESSION, node);
|
|
||||||
target.addElement("parameter").markLocation(loc).setValue(node.toString());
|
|
||||||
lexer.token(")");
|
|
||||||
} else if (lexer.hasToken("(")) {
|
|
||||||
target.makeElement("transform").markLocation(loc).setValue(name);
|
|
||||||
lexer.token("(");
|
|
||||||
if (target.getChildValue("transform").equals(StructureMapTransform.EVALUATE.toCode())) {
|
|
||||||
parseParameterEM(target, lexer);
|
|
||||||
lexer.token(",");
|
|
||||||
loc = lexer.getCurrentLocation();
|
|
||||||
ExpressionNode node = fpe.parse(lexer);
|
|
||||||
target.setUserData(MAP_EXPRESSION, node);
|
|
||||||
target.addElement("parameter").markLocation(loc).setValue(node.toString());
|
|
||||||
} else {
|
|
||||||
while (!lexer.hasToken(")")) {
|
|
||||||
parseParameterEM(target, lexer);
|
|
||||||
if (!lexer.hasToken(")"))
|
|
||||||
lexer.token(",");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lexer.token(")");
|
|
||||||
} else if (name != null) {
|
|
||||||
target.makeElement("transform").markLocation(loc).setValue(StructureMapTransform.COPY.toCode());
|
|
||||||
if (!isConstant) {
|
|
||||||
loc = lexer.getCurrentLocation();
|
|
||||||
String id = name;
|
|
||||||
while (lexer.hasToken(".")) {
|
|
||||||
id = id + lexer.take() + lexer.take();
|
|
||||||
}
|
|
||||||
target.addElement("parameter").markLocation(loc).setValue(id);
|
|
||||||
} else {
|
|
||||||
target.addElement("parameter").markLocation(lexer.getCurrentLocation()).setValue(readConstantEM(name, lexer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lexer.hasToken("as")) {
|
|
||||||
lexer.take();
|
|
||||||
target.makeElement("variable").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) {
|
|
||||||
if (lexer.getCurrent().equals("share")) {
|
|
||||||
target.makeElement("listMode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
target.makeElement("listRuleId").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
} else {
|
|
||||||
target.makeElement("listMode").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void parseParameter(StructureMapGroupRuleDependentComponent ref, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError {
|
private void parseParameter(StructureMapGroupRuleDependentComponent ref, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError {
|
||||||
if (!lexer.isConstant()) {
|
if (!lexer.isConstant()) {
|
||||||
ref.addParameter().setValue(new IdType(lexer.take()));
|
ref.addParameter().setValue(new IdType(lexer.take()));
|
||||||
|
@ -1557,16 +1111,7 @@ public class StructureMapUtilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseParameterEM(Element ref, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError {
|
|
||||||
if (!lexer.isConstant()) {
|
|
||||||
ref.addElement("parameter").markLocation(lexer.getCurrentLocation()).setValue(lexer.take());
|
|
||||||
} else if (lexer.isStringConstant())
|
|
||||||
ref.addElement("parameter").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("??"));
|
|
||||||
else {
|
|
||||||
ref.addElement("parameter").markLocation(lexer.getCurrentLocation()).setValue(readConstantEM(lexer.take(), lexer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DataType readConstant(String s, FHIRLexer lexer) throws FHIRLexerException {
|
private DataType readConstant(String s, FHIRLexer lexer) throws FHIRLexerException {
|
||||||
if (Utilities.isInteger(s))
|
if (Utilities.isInteger(s))
|
||||||
return new IntegerType(s);
|
return new IntegerType(s);
|
||||||
|
@ -1578,16 +1123,6 @@ public class StructureMapUtilities {
|
||||||
return new StringType(lexer.processConstant(s));
|
return new StringType(lexer.processConstant(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String readConstantEM(String s, FHIRLexer lexer) throws FHIRLexerException {
|
|
||||||
if (Utilities.isInteger(s))
|
|
||||||
return s;
|
|
||||||
else if (Utilities.isDecimal(s, false))
|
|
||||||
return s;
|
|
||||||
else if (Utilities.existsInList(s, "true", "false"))
|
|
||||||
return s;
|
|
||||||
else
|
|
||||||
return lexer.processConstant(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public StructureDefinition getTargetType(StructureMap map) throws FHIRException {
|
public StructureDefinition getTargetType(StructureMap map) throws FHIRException {
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
|
|
|
@ -91,24 +91,6 @@ public class StructureMapUtilitiesTest implements ITransformerServices {
|
||||||
assertSerializeDeserialize(map);
|
assertSerializeDeserialize(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSyntaxEM() throws IOException, FHIRException {
|
|
||||||
StructureMapUtilities scu = new StructureMapUtilities(context, this);
|
|
||||||
String fileMap = TestingUtilities.loadTestResource("r5", "structure-mapping", "syntax.map");
|
|
||||||
System.out.println(fileMap);
|
|
||||||
|
|
||||||
Element structureMap = scu.parseEM(fileMap, "Syntax", null);
|
|
||||||
// assertSerializeDeserialize(structureMap);
|
|
||||||
//
|
|
||||||
// String renderedMap = StructureMapUtilities.render(structureMap);
|
|
||||||
// StructureMap map = scu.parse(renderedMap, "Syntax");
|
|
||||||
// System.out.println(map);
|
|
||||||
// assertSerializeDeserialize(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void log(String message) {
|
public void log(String message) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -775,6 +775,34 @@ public class I18nConstants {
|
||||||
public static final String ILLEGAL_COMMENT_TYPE = "ILLEGAL_COMMENT_TYPE";
|
public static final String ILLEGAL_COMMENT_TYPE = "ILLEGAL_COMMENT_TYPE";
|
||||||
public static final String SD_NO_SLICING_ON_ROOT = "SD_NO_SLICING_ON_ROOT";
|
public static final String SD_NO_SLICING_ON_ROOT = "SD_NO_SLICING_ON_ROOT";
|
||||||
public static final String REFERENCE_REF_QUERY_INVALID = "REFERENCE_REF_QUERY_INVALID";
|
public static final String REFERENCE_REF_QUERY_INVALID = "REFERENCE_REF_QUERY_INVALID";
|
||||||
|
public static final String SM_EXTENDS_NOT_SUPPORTED = "";
|
||||||
|
public static final String SM_NAME_INVALID = "SM_NAME_INVALID";
|
||||||
|
public static final String SM_GROUP_INPUT_DUPLICATE = "SM_GROUP_INPUT_DUPLICATE";
|
||||||
|
public static final String SM_GROUP_INPUT_MODE_INVALID = "SM_GROUP_INPUT_MODE_INVALID";
|
||||||
|
public static final String SM_GROUP_INPUT_NO_TYPE = "SM_GROUP_INPUT_NO_TYPE";
|
||||||
|
public static final String SM_GROUP_INPUT_TYPE_NOT_DECLARED = "SM_GROUP_INPUT_TYPE_NOT_DECLARED";
|
||||||
|
public static final String SM_GROUP_INPUT_MODE_MISMATCH = "SM_GROUP_INPUT_MODE_MISMATCH";
|
||||||
|
public static final String SM_GROUP_INPUT_TYPE_UNKNOWN = "SM_GROUP_INPUT_TYPE_UNKNOWN";
|
||||||
|
public static final String SM_SOURCE_CONTEXT_UNKNOWN = "SM_SOURCE_CONTEXT_UNKNOWN";
|
||||||
|
public static final String SM_SOURCE_PATH_INVALID = "SM_SOURCE_PATH_INVALID";
|
||||||
|
public static final String SM_RULE_SOURCE_MIN_REDUNDANT = "SM_RULE_SOURCE_MIN_REDUNDANT";
|
||||||
|
public static final String SM_RULE_SOURCE_MAX_REDUNDANT = "SM_RULE_SOURCE_MAX_REDUNDANT";
|
||||||
|
public static final String SM_RULE_SOURCE_LISTMODE_REDUNDANT = "SM_RULE_SOURCE_LISTMODE_REDUNDANT";
|
||||||
|
public static final String SM_TARGET_CONTEXT_UNKNOWN = "SM_TARGET_CONTEXT_UNKNOWN";
|
||||||
|
public static final String SM_TARGET_PATH_INVALID = "SM_TARGET_PATH_INVALID";
|
||||||
|
public static final String SM_NO_LIST_MODE_NEEDED = "SM_NO_LIST_MODE_NEEDED";
|
||||||
|
public static final String SM_NO_LIST_RULE_ID_NEEDED = "SM_NO_LIST_RULE_ID_NEEDED";
|
||||||
|
public static final String SM_LIST_RULE_ID_ONLY_WHEN_SHARE = "SM_LIST_RULE_ID_ONLY_WHEN_SHARE";
|
||||||
|
public static final String SM_RULE_SOURCE_UNASSIGNED = "SM_RULE_SOURCE_UNASSIGNED";
|
||||||
|
public static final String SM_TARGET_PATH_MULTIPLE_MATCHES = "SM_TARGET_PATH_MULTIPLE_MATCHES";
|
||||||
|
public static final String SM_SOURCE_TYPE_INVALID = "SM_SOURCE_TYPE_INVALID";
|
||||||
|
public static final String SM_TARGET_TRANSFORM_PARAM_COUNT_SINGLE = "SM_TARGET_TRANSFORM_PARAM_COUNT_SINGLE";
|
||||||
|
public static final String SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE = "SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE";
|
||||||
|
public static final String SM_TARGET_TRANSFORM_NOT_CHECKED = "SM_TARGET_TRANSFORM_NOT_CHECKED";
|
||||||
|
public static final String SM_TARGET_NO_TRANSFORM_NO_CHECKED = "SM_TARGET_NO_TRANSFORM_NO_CHECKED";
|
||||||
|
public static final String SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE = "SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE";
|
||||||
|
public static final String SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE = "SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE";
|
||||||
|
public static final String SM_TARGET_TRANSFORM_EXPRESSION_ERROR = "SM_TARGET_TRANSFORM_EXPRESSION_ERROR";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -825,5 +825,34 @@ EXT_VER_URL_REVERSION = The extension URL must not contain a version. The extens
|
||||||
ILLEGAL_COMMENT_TYPE = The fhir_comments property must be an array of strings
|
ILLEGAL_COMMENT_TYPE = The fhir_comments property must be an array of strings
|
||||||
SD_NO_SLICING_ON_ROOT = Slicing is not allowed at the root of a profile
|
SD_NO_SLICING_ON_ROOT = Slicing is not allowed at the root of a profile
|
||||||
REFERENCE_REF_QUERY_INVALID = The query part of the conditional reference is not a valid query string ({0})
|
REFERENCE_REF_QUERY_INVALID = The query part of the conditional reference is not a valid query string ({0})
|
||||||
|
SM_EXTENDS_NOT_SUPPORTED = Group.extends is not supported
|
||||||
|
SM_NAME_INVALID = The name {0} is not valid
|
||||||
|
SM_GROUP_INPUT_DUPLICATE = The name {0} is already used
|
||||||
|
SM_GROUP_INPUT_MODE_INVALID = The group parameter {0} mode {1} isn't valid
|
||||||
|
SM_GROUP_INPUT_NO_TYPE = The group parameter {0} has no type, so the paths cannot be validated
|
||||||
|
SM_GROUP_INPUT_TYPE_NOT_DECLARED = The type {0} was not declared and is unknown
|
||||||
|
SM_GROUP_INPUT_MODE_MISMATCH = The type {0} has mode {1} which doesn't match the structure definition {2}
|
||||||
|
SM_GROUP_INPUT_TYPE_UNKNOWN = The type {0} which maps to the canonical URL {1} is not known, so the paths cannot be validated
|
||||||
|
SM_SOURCE_CONTEXT_UNKNOWN = The source context {0} is not known at this point
|
||||||
|
SM_SOURCE_PATH_INVALID = The source path {0}.{1} refers to the path {2} which is unknown
|
||||||
|
SM_RULE_SOURCE_MIN_REDUNDANT = The min value of {0} is redundant since the valid min is {0}
|
||||||
|
SM_RULE_SOURCE_MAX_REDUNDANT = The max value of {0} is redundant since the valid max is {0}
|
||||||
|
SM_RULE_SOURCE_LISTMODE_REDUNDANT = The listMode value of {0} is redundant since the valid max is {0}
|
||||||
|
SM_TARGET_CONTEXT_UNKNOWN = The target context {0} is not known at this point
|
||||||
|
SM_TARGET_PATH_INVALID = The target path {0}.{1} refers to the path {2} which is unknown
|
||||||
|
SM_NO_LIST_MODE_NEEDED = A list mode should not be provided since this is a rule that can only be executed once
|
||||||
|
SM_NO_LIST_RULE_ID_NEEDED = A list ruleId should not be provided since this is a rule that can only be executed once
|
||||||
|
SM_LIST_RULE_ID_ONLY_WHEN_SHARE = A ruleId should only be provided when the rule mode is 'share'
|
||||||
|
SM_RULE_SOURCE_UNASSIGNED = The source statement doesn't assign a variable to the source - check that this is what is intended
|
||||||
|
SM_TARGET_PATH_MULTIPLE_MATCHES = The target path {0}.{1} refers to the path {2} which is could be a reference to multiple elements ({3}). No further checking can be performed
|
||||||
|
SM_SOURCE_TYPE_INVALID = The type {0} is not valid in this context {1}. The possible types are [{2}]
|
||||||
|
SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE = Transform {0} takes {1}-{2} parameter(s) but {3} were found
|
||||||
|
SM_TARGET_TRANSFORM_PARAM_COUNT_SINGLE = Transform {0} takes {1} parameter(s) but {2} were found
|
||||||
|
SM_TARGET_TRANSFORM_NOT_CHECKED = Transform {0} not checked yet
|
||||||
|
SM_TARGET_NO_TRANSFORM_NO_CHECKED = When there is no transform, parameters can't be provided
|
||||||
|
SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE = The value of the type parameter could not be processed
|
||||||
|
SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE = The parameter at index {0} could not be processed (type = {1})
|
||||||
|
SM_TARGET_TRANSFORM_EXPRESSION_ERROR = The FHIRPath expression passed as the evaluate parameter is invalid: {0}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ public class FTPClientTest implements ResourceLoaderTests {
|
||||||
public static final String DUMMY_FILE_TO_DELETE = "dummyFileToDelete";
|
public static final String DUMMY_FILE_TO_DELETE = "dummyFileToDelete";
|
||||||
|
|
||||||
public static final String DUMMY_FILE_TO_UPLOAD = "dummyFileToUpload";
|
public static final String DUMMY_FILE_TO_UPLOAD = "dummyFileToUpload";
|
||||||
public static final int FAKE_FTP_PORT = 8021;
|
public static final int FAKE_FTP_PORT = 8022;
|
||||||
public static final String DUMMY_FILE_CONTENT = "Dummy file content\nMore content\n";
|
public static final String DUMMY_FILE_CONTENT = "Dummy file content\nMore content\n";
|
||||||
public static final String LOCALHOST = "localhost";
|
public static final String LOCALHOST = "localhost";
|
||||||
|
|
||||||
|
|
|
@ -43,8 +43,8 @@ public class ResourceChecker {
|
||||||
if (Utilities.existsInList(ext, "ttl")) {
|
if (Utilities.existsInList(ext, "ttl")) {
|
||||||
return FhirFormat.TURTLE;
|
return FhirFormat.TURTLE;
|
||||||
}
|
}
|
||||||
if (Utilities.existsInList(ext, "map")) {
|
if (Utilities.existsInList(ext, "map", "fml")) {
|
||||||
return Manager.FhirFormat.TEXT;
|
return Manager.FhirFormat.FML;
|
||||||
}
|
}
|
||||||
if (Utilities.existsInList(ext, "jwt", "jws")) {
|
if (Utilities.existsInList(ext, "jwt", "jws")) {
|
||||||
return Manager.FhirFormat.SHC;
|
return Manager.FhirFormat.SHC;
|
||||||
|
|
|
@ -189,6 +189,7 @@ import org.hl7.fhir.validation.instance.type.MeasureValidator;
|
||||||
import org.hl7.fhir.validation.instance.type.QuestionnaireValidator;
|
import org.hl7.fhir.validation.instance.type.QuestionnaireValidator;
|
||||||
import org.hl7.fhir.validation.instance.type.SearchParameterValidator;
|
import org.hl7.fhir.validation.instance.type.SearchParameterValidator;
|
||||||
import org.hl7.fhir.validation.instance.type.StructureDefinitionValidator;
|
import org.hl7.fhir.validation.instance.type.StructureDefinitionValidator;
|
||||||
|
import org.hl7.fhir.validation.instance.type.StructureMapValidator;
|
||||||
import org.hl7.fhir.validation.instance.type.ValueSetValidator;
|
import org.hl7.fhir.validation.instance.type.ValueSetValidator;
|
||||||
import org.hl7.fhir.validation.instance.utils.ChildIterator;
|
import org.hl7.fhir.validation.instance.utils.ChildIterator;
|
||||||
import org.hl7.fhir.validation.instance.utils.ElementInfo;
|
import org.hl7.fhir.validation.instance.utils.ElementInfo;
|
||||||
|
@ -5000,6 +5001,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
return new SearchParameterValidator(context, timeTracker, fpe, xverManager, jurisdiction).validateSearchParameter(errors, element, stack);
|
return new SearchParameterValidator(context, timeTracker, fpe, xverManager, jurisdiction).validateSearchParameter(errors, element, stack);
|
||||||
} else if (element.getType().equals("StructureDefinition")) {
|
} else if (element.getType().equals("StructureDefinition")) {
|
||||||
return new StructureDefinitionValidator(context, timeTracker, fpe, wantCheckSnapshotUnchanged, xverManager, jurisdiction).validateStructureDefinition(errors, element, stack);
|
return new StructureDefinitionValidator(context, timeTracker, fpe, wantCheckSnapshotUnchanged, xverManager, jurisdiction).validateStructureDefinition(errors, element, stack);
|
||||||
|
} else if (element.getType().equals("StructureMap")) {
|
||||||
|
return new StructureMapValidator(context, timeTracker, fpe, xverManager,profileUtilities, jurisdiction).validateStructureMap(errors, element, stack);
|
||||||
} else if (element.getType().equals("ValueSet")) {
|
} else if (element.getType().equals("ValueSet")) {
|
||||||
return new ValueSetValidator(context, timeTracker, this, xverManager, jurisdiction).validateValueSet(errors, element, stack);
|
return new ValueSetValidator(context, timeTracker, this, xverManager, jurisdiction).validateValueSet(errors, element, stack);
|
||||||
} else {
|
} else {
|
||||||
|
@ -5856,7 +5859,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ToolingExtensions.readBoolExtension(profile, "http://hl7.org/fhir/StructureDefinition/structuredefinition-xml-no-order")) {
|
if (!ToolingExtensions.readBoolExtension(profile, "http://hl7.org/fhir/StructureDefinition/structuredefinition-xml-no-order")) {
|
||||||
boolean ok = (ei.definition == null) || (ei.index >= last) || isXmlAttr;
|
boolean ok = (ei.definition == null) || (ei.index >= last) || isXmlAttr || ei.getElement().isIgnorePropertyOrder();
|
||||||
rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), ok, I18nConstants.VALIDATION_VAL_PROFILE_OUTOFORDER, profile.getVersionedUrl(), ei.getName(), lastei == null ? "(null)" : lastei.getName());
|
rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), ok, I18nConstants.VALIDATION_VAL_PROFILE_OUTOFORDER, profile.getVersionedUrl(), ei.getName(), lastei == null ? "(null)" : lastei.getName());
|
||||||
}
|
}
|
||||||
if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered()) {
|
if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered()) {
|
||||||
|
|
|
@ -0,0 +1,593 @@
|
||||||
|
package org.hl7.fhir.validation.instance.type;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
|
||||||
|
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50;
|
||||||
|
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
|
||||||
|
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
|
||||||
|
import org.hl7.fhir.exceptions.DefinitionException;
|
||||||
|
import org.hl7.fhir.exceptions.FHIRException;
|
||||||
|
import org.hl7.fhir.exceptions.PathEngineException;
|
||||||
|
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
|
||||||
|
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||||
|
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
|
||||||
|
import org.hl7.fhir.r5.elementmodel.Element;
|
||||||
|
import org.hl7.fhir.r5.elementmodel.Manager;
|
||||||
|
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
||||||
|
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
||||||
|
import org.hl7.fhir.r5.model.Coding;
|
||||||
|
import org.hl7.fhir.r5.model.ElementDefinition;
|
||||||
|
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
|
||||||
|
import org.hl7.fhir.r5.model.ExpressionNode;
|
||||||
|
import org.hl7.fhir.r5.model.Extension;
|
||||||
|
import org.hl7.fhir.r5.model.Resource;
|
||||||
|
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||||
|
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
|
||||||
|
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
|
||||||
|
import org.hl7.fhir.r5.model.StructureMap;
|
||||||
|
import org.hl7.fhir.r5.model.TypeDetails;
|
||||||
|
import org.hl7.fhir.r5.model.ValueSet;
|
||||||
|
import org.hl7.fhir.r5.utils.FHIRPathEngine;
|
||||||
|
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
||||||
|
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||||
|
import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException;
|
||||||
|
import org.hl7.fhir.r5.utils.FHIRPathEngine.ElementDefinitionMatch;
|
||||||
|
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||||
|
import org.hl7.fhir.utilities.Utilities;
|
||||||
|
import org.hl7.fhir.utilities.VersionUtilities;
|
||||||
|
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||||
|
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||||
|
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||||
|
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||||
|
import org.hl7.fhir.utilities.validation.ValidationOptions;
|
||||||
|
import org.hl7.fhir.validation.BaseValidator;
|
||||||
|
import org.hl7.fhir.validation.TimeTracker;
|
||||||
|
import org.hl7.fhir.validation.instance.type.StructureMapValidator.ElementDefinitionSource;
|
||||||
|
import org.hl7.fhir.validation.instance.type.StructureMapValidator.RuleInformation;
|
||||||
|
import org.hl7.fhir.validation.instance.type.StructureMapValidator.VariableDefn;
|
||||||
|
import org.hl7.fhir.validation.instance.type.StructureMapValidator.VariableSet;
|
||||||
|
import org.hl7.fhir.validation.instance.utils.NodeStack;
|
||||||
|
import org.jvnet.jaxb2_commons.xml.bind.annotation.adapters.CommaDelimitedStringAdapter;
|
||||||
|
|
||||||
|
public class StructureMapValidator extends BaseValidator {
|
||||||
|
|
||||||
|
public class ElementDefinitionSource {
|
||||||
|
private StructureDefinition sd;
|
||||||
|
private ElementDefinition ed;
|
||||||
|
protected ElementDefinitionSource(StructureDefinition sd, ElementDefinition ed) {
|
||||||
|
super();
|
||||||
|
this.sd = sd;
|
||||||
|
this.ed = ed;
|
||||||
|
}
|
||||||
|
public StructureDefinition getSd() {
|
||||||
|
return sd;
|
||||||
|
}
|
||||||
|
public ElementDefinition getEd() {
|
||||||
|
return ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RuleInformation {
|
||||||
|
|
||||||
|
int maxCount = 1;
|
||||||
|
|
||||||
|
public void seeCardinality(int max) {
|
||||||
|
if (max == Integer.MAX_VALUE || maxCount == Integer.MAX_VALUE) {
|
||||||
|
maxCount = Integer.MAX_VALUE;
|
||||||
|
} else {
|
||||||
|
maxCount = maxCount * max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isList() {
|
||||||
|
return maxCount > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxCount() {
|
||||||
|
return maxCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VariableDefn {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String mode;
|
||||||
|
private int max;
|
||||||
|
private StructureDefinition sd;
|
||||||
|
private ElementDefinition ed;
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
|
||||||
|
protected VariableDefn(String name, String mode) {
|
||||||
|
super();
|
||||||
|
this.name = name;
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableDefn setType(int max, StructureDefinition sd, ElementDefinition ed, String type) {
|
||||||
|
this.max = max;
|
||||||
|
this.sd = sd;
|
||||||
|
this.ed = ed;
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableDefn copy() {
|
||||||
|
VariableDefn n = new VariableDefn(name, mode);
|
||||||
|
n.max = max;
|
||||||
|
n.sd = sd;
|
||||||
|
n.ed = ed;
|
||||||
|
n.type = type;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasTypeInfo() {
|
||||||
|
return sd != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMax() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StructureDefinition getSd() {
|
||||||
|
return sd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ElementDefinition getEd() {
|
||||||
|
return ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VariableSet {
|
||||||
|
|
||||||
|
private List<VariableDefn> list = new ArrayList<>();
|
||||||
|
|
||||||
|
public boolean hasVariable(String name) {
|
||||||
|
for (VariableDefn v : list) {
|
||||||
|
if (name.equals(v.getName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasVariable(String name, boolean source) {
|
||||||
|
for (VariableDefn v : list) {
|
||||||
|
if (name.equals(v.getName()) && source == ("source".equals(v.getMode()))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableDefn add(String name, String mode) {
|
||||||
|
list.removeIf(item -> item.getName().equals(name) && item.getMode().equals(mode));
|
||||||
|
VariableDefn v = new VariableDefn(name, mode);
|
||||||
|
list.add(v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// public void add(VariableDefn v) {
|
||||||
|
// list.removeIf(item -> item.getName().equals(v.getName()) && item.getMode().equals(v.getMode()));
|
||||||
|
// list.add(v);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
public VariableSet copy() {
|
||||||
|
VariableSet set = new VariableSet();
|
||||||
|
for (VariableDefn v : list) {
|
||||||
|
set.list.add(v.copy());
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableDefn getVariable(String name, boolean source) {
|
||||||
|
for (VariableDefn v : list) {
|
||||||
|
if (name.equals(v.getName()) && source == ("source".equals(v.getMode()))) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final boolean SOURCE = true;
|
||||||
|
private static final boolean TARGET = false;
|
||||||
|
|
||||||
|
private FHIRPathEngine fpe;
|
||||||
|
private ProfileUtilities profileUtilities;
|
||||||
|
|
||||||
|
public StructureMapValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, XVerExtensionManager xverManager, ProfileUtilities profileUtilities, Coding jurisdiction) {
|
||||||
|
super(context, xverManager);
|
||||||
|
source = Source.InstanceValidator;
|
||||||
|
this.fpe = fpe;
|
||||||
|
this.timeTracker = timeTracker;
|
||||||
|
this.jurisdiction = jurisdiction;
|
||||||
|
this.profileUtilities = profileUtilities;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateStructureMap(List<ValidationMessage> errors, Element src, NodeStack stack) {
|
||||||
|
boolean ok = true;
|
||||||
|
List<Element> groups = src.getChildrenByName("group");
|
||||||
|
int cc = 0;
|
||||||
|
for (Element group : groups) {
|
||||||
|
ok = validateGroup(errors, src, group, stack.push(group, cc, null, null)) && ok;
|
||||||
|
cc++;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateGroup(List<ValidationMessage> errors, Element src, Element group, NodeStack stack) {
|
||||||
|
String name = group.getChildValue("name");
|
||||||
|
boolean ok = rule(errors, "2023-02-17", IssueType.INVALID, group.line(), group.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name);
|
||||||
|
|
||||||
|
Element extend = src.getNamedChild("extends");
|
||||||
|
if (extend != null) {
|
||||||
|
rule(errors, "2023-02-17", IssueType.NOTSUPPORTED, extend.line(), extend.col(), stack.push(extend, -1, null, null).getLiteralPath(), false, I18nConstants.SM_EXTENDS_NOT_SUPPORTED, extend.primitiveValue());
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VariableSet variables = new VariableSet();
|
||||||
|
|
||||||
|
// first, load all the inputs
|
||||||
|
List<Element> inputs = group.getChildrenByName("input");
|
||||||
|
List<Element> structures = src.getChildrenByName("structure");
|
||||||
|
int cc = 0;
|
||||||
|
for (Element input : inputs) {
|
||||||
|
ok = validateInput(errors, src, group, input, stack.push(input, cc, null, null), structures, variables) && ok;
|
||||||
|
cc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now check the rules.
|
||||||
|
List<Element> rules = group.getChildrenByName("rule");
|
||||||
|
cc = 0;
|
||||||
|
for (Element rule : rules) {
|
||||||
|
ok = validateRule(errors, src, group, rule, stack.push(rule, cc, null, null), variables) && ok;
|
||||||
|
cc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateInput(List<ValidationMessage> errors, Element src, Element group, Element input, NodeStack stack, List<Element> structures, VariableSet variables) {
|
||||||
|
boolean ok = false;
|
||||||
|
String name = input.getChildValue("name");
|
||||||
|
String type = input.getChildValue("type");
|
||||||
|
String mode = input.getChildValue("mode");
|
||||||
|
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name) && // the name {0} is not valid)
|
||||||
|
rule(errors, "2023-02-17", IssueType.DUPLICATE, input.line(), input.col(), stack.getLiteralPath(), !variables.hasVariable(name), I18nConstants.SM_GROUP_INPUT_DUPLICATE, name)) { // the name {0} is not valid)
|
||||||
|
VariableDefn v = variables.add(name, mode);
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.INVALID, input.line(), input.col(), stack.getLiteralPath(), Utilities.existsInList(mode, "source", "target"), I18nConstants.SM_GROUP_INPUT_MODE_INVALID, name, mode) && // the group parameter {0} mode {1} isn't valid
|
||||||
|
warning(errors, "2023-02-17", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), type != null, I18nConstants.SM_GROUP_INPUT_NO_TYPE, name)) { // the group parameter {0} has no type, so the paths cannot be validated
|
||||||
|
Element structure = findStructure(structures, type);
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), structure != null, I18nConstants.SM_GROUP_INPUT_TYPE_NOT_DECLARED, type)) { // the type {0} was not declared and is unknown
|
||||||
|
String url = structure.getChildValue("url");
|
||||||
|
String smode = structure.getChildValue("mode");
|
||||||
|
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), mode.equals(smode), I18nConstants.SM_GROUP_INPUT_MODE_MISMATCH, type, mode, smode) && // the type {0} has mode {1} which doesn't match the structure definition {2}
|
||||||
|
rule(errors, "2023-02-17", IssueType.INVALID, input.line(), input.col(), stack.getLiteralPath(), sd != null, I18nConstants.SM_GROUP_INPUT_TYPE_UNKNOWN, type, url)) { // the type {0} which maps to the canonical URL {1} is not known, so the paths cannot be validated
|
||||||
|
v.setType(1, sd, sd.getSnapshot().getElementFirstRep(), null);
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Element findStructure(List<Element> structures, String type) {
|
||||||
|
for (Element structure : structures ) {
|
||||||
|
String t = structure.getChildValue("alias");
|
||||||
|
if (type.equals(t)) {
|
||||||
|
return structure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean idIsValid(String name) {
|
||||||
|
return name != null && name.matches("[a-zA-Z][a-zA-Z0-9]*");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateRule(List<ValidationMessage> errors, Element src, Element group, Element rule, NodeStack stack, VariableSet variables) {
|
||||||
|
String name = rule.getChildValue("name");
|
||||||
|
boolean ok = rule(errors, "2023-02-17", IssueType.INVALID, rule.line(), rule.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name);
|
||||||
|
|
||||||
|
RuleInformation ruleInfo = new RuleInformation();
|
||||||
|
// process the sources
|
||||||
|
VariableSet lvars = variables.copy();
|
||||||
|
List<Element> sources = rule.getChildrenByName("source");
|
||||||
|
int cc = 0;
|
||||||
|
for (Element source : sources) {
|
||||||
|
ok = validateRuleSource(errors, src, group, rule, source, stack.push(source, cc, null, null), lvars, ruleInfo) && ok;
|
||||||
|
cc++;
|
||||||
|
}
|
||||||
|
// process the targets
|
||||||
|
List<Element> targets = rule.getChildrenByName("target");
|
||||||
|
cc = 0;
|
||||||
|
for (Element target : targets) {
|
||||||
|
ok = validateRuleTarget(errors, src, group, rule, target, stack.push(target, cc, null, null), lvars, ruleInfo) && ok;
|
||||||
|
cc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process the targets
|
||||||
|
List<Element> rules = rule.getChildrenByName("rule");
|
||||||
|
cc = 0;
|
||||||
|
for (Element child : rules) {
|
||||||
|
ok = validateRule(errors, src, group, child, stack.push(child, cc, null, null), lvars) && ok;
|
||||||
|
cc++;
|
||||||
|
}
|
||||||
|
// todo: check dependents
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateRuleSource(List<ValidationMessage> errors, Element src, Element group, Element rule, Element source, NodeStack stack, VariableSet variables, RuleInformation ruleInfo) {
|
||||||
|
String context = source.getChildValue("context");
|
||||||
|
boolean ok = rule(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), idIsValid(context), I18nConstants.SM_NAME_INVALID, context) &&
|
||||||
|
rule(errors, "2023-02-17", IssueType.UNKNOWN, source.line(), source.col(), stack.getLiteralPath(), variables.hasVariable(context, SOURCE), I18nConstants.SM_SOURCE_CONTEXT_UNKNOWN, context);
|
||||||
|
if (ok) {
|
||||||
|
VariableDefn v = variables.getVariable(context, SOURCE);
|
||||||
|
if (v.hasTypeInfo()) { // if it doesn't, that's already an issue elsewhere
|
||||||
|
// check type
|
||||||
|
// check defaultValue
|
||||||
|
// check element
|
||||||
|
String element = source.getChildValue("element");
|
||||||
|
if (element != null) {
|
||||||
|
String path = v.getEd().getPath()+"."+element;
|
||||||
|
String variable = source.getChildValue("variable");
|
||||||
|
VariableDefn vn = null;
|
||||||
|
if (hint(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), variable != null, I18nConstants.SM_RULE_SOURCE_UNASSIGNED)) {
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), idIsValid(variable), I18nConstants.SM_NAME_INVALID, variable)) {
|
||||||
|
vn = variables.add(variable, v.getMode()); // may overwrite
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ElementDefinitionSource> els = getElementDefinitions(v.getSd(), v.getEd(), v.getType(), element);
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), !els.isEmpty(), I18nConstants.SM_SOURCE_PATH_INVALID, context, element, path)) {
|
||||||
|
if (warning(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), els.size() == 1, I18nConstants.SM_TARGET_PATH_MULTIPLE_MATCHES, context, element, v.getEd().getPath()+"."+element, render(els))) {
|
||||||
|
ElementDefinitionSource el = els.get(0);
|
||||||
|
String type = source.getChildValue("type");
|
||||||
|
if (type != null) {
|
||||||
|
ok = rule(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), hasType(el.getEd(), type), I18nConstants.SM_SOURCE_TYPE_INVALID, type, path, el.getEd().typeSummary()) && ok;
|
||||||
|
}
|
||||||
|
String min = source.getChildValue("min");
|
||||||
|
hint(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), min == null || isMoreOrEqual(min, v.getEd().getMin()), I18nConstants.SM_RULE_SOURCE_MIN_REDUNDANT, min, v.getEd().getMin());
|
||||||
|
|
||||||
|
int existingMax = multiplyCardinality(v.getMax(), el.getEd().getMax());
|
||||||
|
String max = source.getChildValue("max");
|
||||||
|
int iMax = readMax(max, existingMax);
|
||||||
|
warning(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), iMax <= existingMax, I18nConstants.SM_RULE_SOURCE_MAX_REDUNDANT, max, v.getMax());
|
||||||
|
ruleInfo.seeCardinality(iMax);
|
||||||
|
|
||||||
|
|
||||||
|
if (vn != null) {
|
||||||
|
vn.setType(iMax, el.getSd(), el.getEd(), type); // may overwrite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check condition
|
||||||
|
// check check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasType(ElementDefinition ed, String type) {
|
||||||
|
for (TypeRefComponent td : ed.getType()) {
|
||||||
|
if (type.equals(td.getWorkingCode())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readMax(String max, int existingMax) {
|
||||||
|
if (max == null || !Utilities.isInteger(max)) {
|
||||||
|
return existingMax;
|
||||||
|
} else {
|
||||||
|
return Integer.parseInt(max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int multiplyCardinality(int max, String max2) {
|
||||||
|
if (max == Integer.MAX_VALUE || "*".equals(max2)) {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
} else {
|
||||||
|
return max * Integer.parseInt(max2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateRuleTarget(List<ValidationMessage> errors, Element src, Element group, Element rule, Element target, NodeStack stack, VariableSet variables, RuleInformation ruleInfo) {
|
||||||
|
String context = target.getChildValue("context");
|
||||||
|
if (context == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
boolean ok = rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), idIsValid(context), I18nConstants.SM_NAME_INVALID, context) &&
|
||||||
|
rule(errors, "2023-02-17", IssueType.UNKNOWN, target.line(), target.col(), stack.getLiteralPath(), variables.hasVariable(context, TARGET), I18nConstants.SM_TARGET_CONTEXT_UNKNOWN, context);
|
||||||
|
if (ok) {
|
||||||
|
VariableDefn v = variables.getVariable(context, TARGET);
|
||||||
|
if (v.hasTypeInfo()) {
|
||||||
|
String listMode = target.getChildValue("listMode");
|
||||||
|
String listRuleId = target.getChildValue("listRuleId");
|
||||||
|
warning(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), listRuleId == null || "share".equals(listMode), I18nConstants.SM_LIST_RULE_ID_ONLY_WHEN_SHARE);
|
||||||
|
if (!ruleInfo.isList()) {
|
||||||
|
warning(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), listMode == null, I18nConstants.SM_NO_LIST_MODE_NEEDED);
|
||||||
|
warning(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), listRuleId == null, I18nConstants.SM_NO_LIST_RULE_ID_NEEDED);
|
||||||
|
}
|
||||||
|
VariableDefn vn = null;
|
||||||
|
String variable = target.getChildValue("variable");
|
||||||
|
if (variable != null) {
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), idIsValid(variable), I18nConstants.SM_NAME_INVALID, variable)) {
|
||||||
|
vn = variables.add(variable, v.getMode()); // may overwrite
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String element = target.getChildValue("element");
|
||||||
|
if (element != null) {
|
||||||
|
List<ElementDefinitionSource> els = getElementDefinitions(v.getSd(), v.getEd(), v.getType(), element);
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), !els.isEmpty(), I18nConstants.SM_TARGET_PATH_INVALID, context, element, v.getEd().getPath()+"."+element)) {
|
||||||
|
if (warning(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), els.size() == 1, I18nConstants.SM_TARGET_PATH_MULTIPLE_MATCHES, context, element, v.getEd().getPath()+"."+element, render(els))) {
|
||||||
|
ElementDefinitionSource el = els.get(0);
|
||||||
|
String type = null; // maybe inferred / derived from transform in the future
|
||||||
|
String transform = target.getChildValue("transform");
|
||||||
|
List<Element> params = target.getChildren("parameter");
|
||||||
|
if (transform == null) {
|
||||||
|
rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 0, I18nConstants.SM_TARGET_NO_TRANSFORM_NO_CHECKED, transform);
|
||||||
|
} else {
|
||||||
|
switch (transform) {
|
||||||
|
case "create":
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() < 2, I18nConstants.SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE, "create", "0", "1", params.size())) {
|
||||||
|
if (params.size() == 1) {
|
||||||
|
type = params.get(0).primitiveValue();
|
||||||
|
warning(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(),type != null, I18nConstants.SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE);
|
||||||
|
} else {
|
||||||
|
// maybe can guess? maybe not ... type =
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "reference":
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 1, I18nConstants.SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE, "reference", "0", "1", params.size())) {
|
||||||
|
type = "string";
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "evaluate":
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 1, I18nConstants.SM_TARGET_TRANSFORM_PARAM_COUNT_SINGLE, "evaluate", "1", params.size())) {
|
||||||
|
String exp = params.get(0).primitiveValue();
|
||||||
|
if (rule(errors, "2023-02-17", IssueType.INVALID, params.get(0).line(), params.get(0).col(), stack.getLiteralPath(), exp != null, I18nConstants.SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE, "0", params.size())) {
|
||||||
|
try {
|
||||||
|
TypeDetails td = fpe.check(null, v.getSd().getType(), v.getEd().getPath(), fpe.parse(exp));
|
||||||
|
if (td.getTypes().size() == 1) {
|
||||||
|
type = td.getType();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
rule(errors, "2023-02-17", IssueType.INVALID, params.get(0).line(), params.get(0).col(), stack.getLiteralPath(), false, I18nConstants.SM_TARGET_TRANSFORM_EXPRESSION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), false, I18nConstants.SM_TARGET_TRANSFORM_NOT_CHECKED, transform);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//todo: transform / parameter
|
||||||
|
if (vn != null) {
|
||||||
|
vn.setType(ruleInfo.getMaxCount(), el.getSd(), el.getEd(), type); // may overwrite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String render(List<ElementDefinitionSource> list) {
|
||||||
|
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||||
|
for (ElementDefinitionSource t : list) {
|
||||||
|
b.append(t.getEd().getId());
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ElementDefinitionSource> getElementDefinitions(StructureDefinition sd, ElementDefinition ed, String type, String element) {
|
||||||
|
List<ElementDefinitionSource> result = new ArrayList<>();
|
||||||
|
List<ElementDefinition> children = profileUtilities.getChildList(sd, ed);
|
||||||
|
if (children == null || children.isEmpty()) {
|
||||||
|
getElementDefinitionChildrenFromTypes(result, sd, ed, type, element);
|
||||||
|
} else {
|
||||||
|
for (ElementDefinition t : children) {
|
||||||
|
if (t.getNameBase().equals(element)) {
|
||||||
|
result.add(new ElementDefinitionSource(sd, t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getElementDefinitionChildrenFromTypes(List<ElementDefinitionSource> result, StructureDefinition sd, ElementDefinition ed, String type, String element) {
|
||||||
|
for (TypeRefComponent td : ed.getType()) {
|
||||||
|
if (type == null | td.getWorkingCode().equals(type)) {
|
||||||
|
StructureDefinition tsd = context.fetchTypeDefinition(td.getWorkingCode());
|
||||||
|
if (tsd != null) {
|
||||||
|
for (ElementDefinition t : tsd.getSnapshot().getElement()) {
|
||||||
|
if (Utilities.charCount(t.getPath(), '.') == 1 && t.getNameBase().equals(element)) {
|
||||||
|
result.add(new ElementDefinitionSource(tsd, t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("Unable to find type "+type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLessOrEqual(String value, String limit) {
|
||||||
|
if (Utilities.isInteger(value) || !Utilities.isInteger(limit)) {
|
||||||
|
int v = Integer.parseInt(value);
|
||||||
|
int l = Integer.parseInt(limit);
|
||||||
|
return v <= l;
|
||||||
|
}
|
||||||
|
return true; // no issue in this case
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMoreOrEqual(String value, int limit) {
|
||||||
|
if (Utilities.isInteger(value)) {
|
||||||
|
int v = Integer.parseInt(value);
|
||||||
|
return v >= limit;
|
||||||
|
}
|
||||||
|
return true; // no issue in this case
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue