Merge pull request #1138 from hapifhir/gg-202302-map-validation

FML validation
This commit is contained in:
Grahame Grieve 2023-02-25 16:41:26 +11:00 committed by GitHub
commit edf5ecf275
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1275 additions and 505 deletions

View File

@ -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);
}
}
}
} }

View File

@ -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);
}
}

View File

@ -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;
} }

View File

@ -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() {

View File

@ -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
} }

View File

@ -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()) {

View File

@ -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;

View File

@ -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) {
} }

View File

@ -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";
} }

View File

@ -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}

View File

@ -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";

View File

@ -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;

View File

@ -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()) {

View File

@ -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
}
}