From c9833f94d3e1f71d64c09db851ac786c1a039cc2 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 18 Jan 2023 12:32:06 +1100 Subject: [PATCH] Add ElementModel based StructureMap parser --- .../org/hl7/fhir/r5/elementmodel/Element.java | 58 ++- .../org/hl7/fhir/r5/elementmodel/Manager.java | 2 +- .../java/org/hl7/fhir/r5/utils/FHIRLexer.java | 6 + .../structuremap/StructureMapUtilities.java | 481 +++++++++++++++++- .../r5/test/StructureMapUtilitiesTest.java | 16 + 5 files changed, 558 insertions(+), 5 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java index dfb1fe5f4..bd4f04a37 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java @@ -59,6 +59,7 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.utilities.ElementDecoration; import org.hl7.fhir.utilities.ElementDecoration.DecorationType; +import org.hl7.fhir.utilities.SourceLocation; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.xhtml.XhtmlNode; @@ -509,8 +510,12 @@ public class Element extends Base { public Base makeProperty(int hash, String name) throws FHIRException { if (isPrimitive() && (hash == "value".hashCode())) { return new StringType(value); + } else { + return makeElement(name); } + } + public Element makeElement(String name) throws FHIRException { if (children == null) children = new ArrayList(); @@ -538,7 +543,30 @@ public class Element extends Base { throw new Error("Unrecognised name "+name+" on "+this.name); } - + + public Element forceElement(String name) throws FHIRException { + if (children == null) + children = new ArrayList(); + + // look through existing children + for (Element child : children) { + if (child.getName().equals(name)) { + return child; + } + } + + for (Property p : property.getChildProperties(this.name, type)) { + if (p.getName().equals(name)) { + Element ne = new Element(name, p); + children.add(ne); + return ne; + } + } + + throw new Error("Unrecognised name "+name+" on "+this.name); + } + + private int maxToInt(String max) { if (max.equals("*")) return Integer.MAX_VALUE; @@ -598,6 +626,12 @@ public class Element extends Base { return this; } + public Element markLocation(SourceLocation loc) { + this.line = loc.getLine(); + this.col = loc.getColumn(); + return this; + } + public void clearDecorations() { clearUserData("fhir.decorations"); for (Element e : children) { @@ -1258,5 +1292,27 @@ public class Element extends Base { return fhirType(); } } + + public void setElement(String string, Element map) { + throw new Error("Not done yet"); + } + + public Element addElement(String name) { + if (children == null) + children = new ArrayList(); + + for (Property p : property.getChildProperties(this.name, type)) { + if (p.getName().equals(name)) { + if (!p.isList()) { + throw new Error(name+" on "+this.name+" is not a list, so can't add an element"); + } + Element ne = new Element(name, p); + children.add(ne); + return ne; + } + } + + throw new Error("Unrecognised name "+name+" on "+this.name); + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java index 81614275a..34c93c9df 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java @@ -122,7 +122,7 @@ public class Manager { public static Element build(IWorkerContext context, StructureDefinition sd) { Property p = new Property(context, sd.getSnapshot().getElementFirstRep(), sd); - Element e = new Element(null, p); + Element e = new Element(p.getName(), p); return e; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java index 8ea18528c..9ac1d7a70 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java @@ -77,6 +77,7 @@ public class FHIRLexer { private int id; private String name; private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host + private SourceLocation commentLocation; public FHIRLexer(String source, String name) throws FHIRLexerException { this.source = source == null ? "" : source; @@ -298,12 +299,14 @@ public class FHIRLexer { boolean done = false; while (cursor < source.length() && !done) { if (cursor < source.length() -1 && "//".equals(source.substring(cursor, cursor+2))) { + commentLocation = currentLocation; int start = cursor+2; while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) { cursor++; } comments.add(source.substring(start, cursor).trim()); } else if (cursor < source.length() - 1 && "/*".equals(source.substring(cursor, cursor+2))) { + commentLocation = currentLocation; int start = cursor+2; while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor+2))) { last13 = currentLocation.checkChar(source.charAt(cursor), last13); @@ -533,5 +536,8 @@ public class FHIRLexer { public void setLiquidMode(boolean liquidMode) { this.liquidMode = liquidMode; } + public SourceLocation getCommentLocation() { + return this.commentLocation; + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java index bde738d20..e893dcb0f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java @@ -42,6 +42,7 @@ import org.hl7.fhir.r5.context.ContextUtilities; 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.Property; import org.hl7.fhir.r5.model.*; import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; @@ -68,6 +69,7 @@ import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException; import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.SourceLocation; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationOptions; @@ -638,6 +640,31 @@ public class StructureMapUtilities { return result; } + public Element parseEM(String text, String srcName) 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")); + 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); + } + + return result; + } + private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException { lexer.token("conceptmap"); ConceptMap map = new ConceptMap(); @@ -694,6 +721,64 @@ public class StructureMapUtilities { 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 prefixes = new HashMap(); + 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) { for (ConceptMapGroupComponent grp : map.getGroup()) { if (grp.getSource().equals(srcs)) @@ -708,6 +793,23 @@ public class StructureMapUtilities { grp.setTarget(tgts); 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 prefixes, FHIRLexer lexer) throws FHIRLexerException { @@ -736,7 +838,6 @@ public class StructureMapUtilities { private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException { lexer.token("uses"); - int currentLine = lexer.getCurrentLocation().getLine(); StructureMapStructureComponent st = result.addStructure(); st.setUrl(lexer.readConstant("url")); if (lexer.hasToken("alias")) { @@ -748,13 +849,33 @@ public class StructureMapUtilities { lexer.skipToken(";"); 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 { lexer.token("imports"); - int currentLine = lexer.getCurrentLocation().getLine(); result.addImport(lexer.readConstant("url")); lexer.skipToken(";"); - lexer.getFirstComment(); + } + + 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 { @@ -829,6 +950,79 @@ public class StructureMapUtilities { 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 { StructureMapGroupInputComponent input = group.addInput(); if (newFmt) { @@ -848,6 +1042,27 @@ 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 list, FHIRLexer lexer, boolean newFmt) throws FHIRException { StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); if (!newFmt) { @@ -923,6 +1138,84 @@ 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) { return (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) && @@ -930,6 +1223,15 @@ public class StructureMapUtilities { (rule.getDependent().size() == 0 && rule.getRule().size() == 0); } + + 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 { StructureMapGroupRuleDependentComponent ref = rule.addDependent(); ref.setName(lexer.take()); @@ -944,6 +1246,20 @@ public class StructureMapUtilities { 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 { StructureMapGroupRuleSourceComponent source = rule.addSource(); source.setContext(lexer.take()); @@ -999,6 +1315,66 @@ 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 { StructureMapGroupRuleTargetComponent target = rule.addTarget(); String start = lexer.take(); @@ -1073,6 +1449,84 @@ 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 { if (!lexer.isConstant()) { @@ -1094,6 +1548,16 @@ 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 { if (Utilities.isInteger(s)) return new IntegerType(s); @@ -1105,6 +1569,17 @@ public class StructureMapUtilities { 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 { boolean found = false; StructureDefinition res = null; diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/StructureMapUtilitiesTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/StructureMapUtilitiesTest.java index 04163b4f0..adba23e34 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/StructureMapUtilitiesTest.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/StructureMapUtilitiesTest.java @@ -92,6 +92,22 @@ public class StructureMapUtilitiesTest implements ITransformerServices { } + @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"); +// assertSerializeDeserialize(structureMap); +// +// String renderedMap = StructureMapUtilities.render(structureMap); +// StructureMap map = scu.parse(renderedMap, "Syntax"); +// System.out.println(map); +// assertSerializeDeserialize(map); + } + + @Override public void log(String message) {