Merge pull request #1161 from hapifhir/gg-202303-qa-sm-again

Gg 202303 qa sm again
This commit is contained in:
Grahame Grieve 2023-03-09 21:34:56 +11:00 committed by GitHub
commit b36a376d43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 803 additions and 26 deletions

View File

@ -146,6 +146,7 @@ public class FmlParser extends ParserBase {
throw lexer.error("Only unmapped mode PROVIDED is supported at this time");
}
while (!lexer.hasToken("}")) {
String comments = lexer.hasComments() ? lexer.getAllComments() : null;
String srcs = readPrefix(prefixes, lexer);
lexer.token(":");
SourceLocation scloc = lexer.getCurrentLocation();
@ -155,6 +156,11 @@ public class FmlParser extends ParserBase {
String tgts = readPrefix(prefixes, lexer);
Element g = getGroupE(map, srcs, tgts);
Element e = g.addElement("element");
if (comments != null) {
for (String s : comments.split("\\r\\n")) {
e.getComments().add(s);
}
}
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());

View File

@ -63,6 +63,7 @@ public class ResourceParser extends ParserBase {
private void parseChildren(String path, Base src, Element dst) {
dst.setSource(src);
dst.copyFormatComments(src);
List<Property> properties = dst.getProperty().getChildProperties(dst.getName(), null);
for (org.hl7.fhir.r5.model.Property c : src.children()) {
if (c.hasValues()) {

View File

@ -9,6 +9,7 @@ import java.util.Map;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import ca.uhn.fhir.model.api.IElement;
@ -105,7 +106,9 @@ public abstract class Base implements Serializable, IBase, IElement {
/**
* Round tracking xml comments for testing convenience
*/
private List<String> formatCommentsPost;
private List<String> formatCommentsPost;
private List<ValidationMessage> validationMessages;
public Object getUserData(String name) {
@ -167,7 +170,15 @@ public abstract class Base implements Serializable, IBase, IElement {
}
public boolean hasFormatComment() {
return (formatCommentsPre != null && !formatCommentsPre.isEmpty()) || (formatCommentsPost != null && !formatCommentsPost.isEmpty());
return hasFormatCommentPre() || hasFormatCommentPost();
}
public boolean hasFormatCommentPre() {
return formatCommentsPre != null && !formatCommentsPre.isEmpty();
}
public boolean hasFormatCommentPost() {
return formatCommentsPost != null && !formatCommentsPost.isEmpty();
}
public List<String> getFormatCommentsPre() {
@ -182,6 +193,29 @@ public abstract class Base implements Serializable, IBase, IElement {
return formatCommentsPost;
}
public void copyFormatComments(Base other) {
if (other.hasFormatComment()) {
formatCommentsPre = new ArrayList<>();
formatCommentsPre.addAll(other.formatCommentsPre);
} else {
formatCommentsPre = null;
}
}
public void addFormatCommentsPre(List<String> comments) {
if (comments != null && !comments.isEmpty()) {
getFormatCommentsPre().addAll(comments);
}
}
public void addFormatCommentsPost(List<String> comments) {
if (comments != null && !comments.isEmpty()) {
getFormatCommentsPost().addAll(comments);
}
}
// these 3 allow evaluation engines to get access to primitive values
public boolean isPrimitive() {
return false;
@ -463,4 +497,24 @@ public abstract class Base implements Serializable, IBase, IElement {
this.validationInfo.add(vi);
return vi;
}
// validation messages: the validator does not populate these (yet)
public Base addValidationMessage(ValidationMessage msg) {
if (validationMessages == null) {
validationMessages = new ArrayList<>();
}
validationMessages.add(msg);
return this;
}
public boolean hasValidationMessages() {
return validationMessages != null && !validationMessages.isEmpty();
}
public List<ValidationMessage> getValidationMessages() {
return validationMessages != null ? validationMessages : new ArrayList<>();
}
}

View File

@ -91,6 +91,9 @@ public class RendererFactory {
if ("Requirements".equals(resourceName)) {
return new RequirementsRenderer(context);
}
if ("StructureMap".equals(resourceName)) {
return new StructureMapRenderer(context);
}
return new ProfileDrivenRenderer(context);
}

View File

@ -183,7 +183,7 @@ public class SearchParameterRenderer extends TerminologyRenderer {
@Override
public String display(Resource r) throws UnsupportedEncodingException, IOException {
return ((OperationDefinition) r).present();
return ((SearchParameter) r).present();
}
}

View File

@ -0,0 +1,644 @@
package org.hl7.fhir.r5.renderers;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.Enumeration;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
import org.hl7.fhir.r5.model.Enumerations.SearchComparator;
import org.hl7.fhir.r5.model.Enumerations.SearchModifierCode;
import org.hl7.fhir.r5.model.Enumerations.VersionIndependentResourceTypesAll;
import org.hl7.fhir.r5.model.OperationDefinition;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.model.SearchParameter.SearchParameterComponentComponent;
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupComponent;
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupInputComponent;
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleComponent;
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleDependentComponent;
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleSourceComponent;
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleTargetComponent;
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleTargetParameterComponent;
import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent;
import org.hl7.fhir.r5.model.StructureMap.StructureMapTargetListMode;
import org.hl7.fhir.r5.model.StructureMap.StructureMapTransform;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureMap;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType;
import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext;
import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.xhtml.XhtmlFluent;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class StructureMapRenderer extends TerminologyRenderer {
private static final String COLOR_COMMENT = "green";
private static final String COLOR_METADATA = "#cc00cc";
private static final String COLOR_CONST = "blue";
private static final String COLOR_VARIABLE = "maroon";
private static final String COLOR_SYNTAX = "navy";
private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true;
private static final String COLOR_SPECIAL = "#b36b00";
private static final String DEFAULT_COMMENT = "This element was not defined prior to R5";
private String clauseComment = DEFAULT_COMMENT;
public StructureMapRenderer(RenderingContext context) {
super(context);
}
public StructureMapRenderer(RenderingContext context, ResourceContext rcontext) {
super(context, rcontext);
}
public boolean render(XhtmlNode x, Resource dr) throws IOException, FHIRException, EOperationOutcome {
return render(x, (StructureMap) dr);
}
public boolean render(XhtmlNode x, StructureMap map) throws IOException, FHIRException, EOperationOutcome {
renderMap(x.pre("fml"), map);
return false;
}
public void renderMap(XhtmlNode x, StructureMap map) {
x.tx("\r\n");
if (VersionUtilities.isR5Plus(context.getContext().getVersion())) {
renderMetadata(x, "url", map.getUrlElement());
renderMetadata(x, "name", map.getNameElement());
renderMetadata(x, "title", map.getTitleElement());
renderMetadata(x, "url", map.getStatusElement(), "draft");
x.tx("\r\n");
} else {
x.b().tx("map");
x.color(COLOR_SYNTAX).tx(" \"");
x.tx(map.getUrl());
x.color(COLOR_SYNTAX).tx("\" = \"");
x.tx(Utilities.escapeJson(map.getName()));
x.color(COLOR_SYNTAX).tx("\"\r\n\r\n");
if (map.getDescription() != null) {
renderMultilineDoco(x, map.getDescription(), 0, null);
x.tx("\r\n");
}
}
renderConceptMaps(x, map);
renderUses(x, map);
renderImports(x, map);
for (StructureMapGroupComponent g : map.getGroup())
renderGroup(x, g);
}
private void renderMetadata(XhtmlNode x, String name, DataType value) {
renderMetadata(x, name, value, null);
}
private void renderMetadata(XhtmlNode x, String name, DataType value, String def) {
String v = value.primitiveValue();
if (def == null || !def.equals(v)) {
XhtmlNode c = x.color(COLOR_METADATA);
c.tx("/// ");
c.b().tx(name);
c.tx(" = ");
if (Utilities.existsInList(v, "true", "false") || Utilities.isDecimal(v, true)) {
x.color(COLOR_CONST).tx(v);
} else {
x.color(COLOR_CONST).tx("'"+v+"'");
}
x.tx("\r\n");
}
}
private void renderConceptMaps(XhtmlNode x,StructureMap map) {
for (Resource r : map.getContained()) {
if (r instanceof ConceptMap) {
produceConceptMap(x, (ConceptMap) r);
}
}
}
private void produceConceptMap(XhtmlNode x,ConceptMap cm) {
if (cm.hasFormatCommentPre()) {
renderMultilineDoco(x, cm.getFormatCommentsPre(), 0, null);
}
x.b().tx("conceptmap");
x.color(COLOR_SYNTAX).tx(" \"");
x.tx(cm.getId());
x.color(COLOR_SYNTAX).tx("\" {\r\n");
Map<String, String> prefixesSrc = new HashMap<String, String>();
Map<String, String> prefixesTgt = new HashMap<String, String>();
char prefix = 's';
for (ConceptMapGroupComponent cg : cm.getGroup()) {
if (!prefixesSrc.containsKey(cg.getSource())) {
prefixesSrc.put(cg.getSource(), String.valueOf(prefix));
x.b().tx(" prefix ");
x.tx(prefix);
x.color(COLOR_SYNTAX).tx(" = \"");
x.tx(""+cg.getSource());
x.color(COLOR_SYNTAX).tx("\"\r\n");
prefix++;
}
if (!prefixesTgt.containsKey(cg.getTarget())) {
prefixesTgt.put(cg.getTarget(), String.valueOf(prefix));
x.b().tx(" prefix ");
x.tx(prefix);
x.color(COLOR_SYNTAX).tx(" = \"");
x.tx(""+cg.getTarget());
x.color(COLOR_SYNTAX).tx("\"\r\n");
prefix++;
}
}
x.tx("\r\n");
for (ConceptMapGroupComponent cg : cm.getGroup()) {
if (cg.hasUnmapped()) {
x.b().tx(" unmapped for ");
x.tx(prefixesSrc.get(cg.getSource()));
x.color(COLOR_SYNTAX).tx(" = ");
x.tx(cg.getUnmapped().getMode().toCode());
x.tx("\r\n");
}
}
for (ConceptMapGroupComponent cg : cm.getGroup()) {
if (cg.hasFormatCommentPre()) {
renderMultilineDoco(x, cg.getFormatCommentsPre(), 2, prefixesSrc.values());
}
for (SourceElementComponent ce : cg.getElement()) {
if (ce.hasFormatCommentPre()) {
renderMultilineDoco(x, ce.getFormatCommentsPre(), 2, prefixesSrc.values());
}
x.tx(" ");
x.tx(prefixesSrc.get(cg.getSource()));
x.color(COLOR_SYNTAX).tx(":");
if (Utilities.isToken(ce.getCode())) {
x.tx(ce.getCode());
} else {
x.tx("\"");
x.tx(ce.getCode());
x.tx("\"");
}
x.tx(" ");
x.b().tx(getChar(ce.getTargetFirstRep().getRelationship()));
x.tx(" ");
x.tx(prefixesTgt.get(cg.getTarget()));
x.color(COLOR_SYNTAX).tx(":");
if (Utilities.isToken(ce.getTargetFirstRep().getCode())) {
x.tx(ce.getTargetFirstRep().getCode());
} else {
x.color(COLOR_SYNTAX).tx("\"");
x.tx(ce.getTargetFirstRep().getCode());
x.color(COLOR_SYNTAX).tx("\"");
}
x.tx("\r\n");
if (ce.hasFormatCommentPost()) {
renderMultilineDoco(x, ce.getFormatCommentsPost(), 2, prefixesSrc.values());
}
}
if (cg.hasFormatCommentPost()) {
renderMultilineDoco(x, cg.getFormatCommentsPost(), 2, prefixesSrc.values());
}
}
if (cm.hasFormatCommentPost()) {
renderMultilineDoco(x, cm.getFormatCommentsPost(), 2, prefixesSrc.values());
}
x.color(COLOR_SYNTAX).tx("}\r\n\r\n");
}
private String getChar(ConceptMapRelationship relationship) {
switch (relationship) {
case RELATEDTO:
return "-";
case EQUIVALENT:
return "==";
case NOTRELATEDTO:
return "!=";
case SOURCEISNARROWERTHANTARGET:
return "<=";
case SOURCEISBROADERTHANTARGET:
return ">=";
default:
return "??";
}
}
private void renderUses(XhtmlNode x,StructureMap map) {
for (StructureMapStructureComponent s : map.getStructure()) {
x.b().tx("uses");
x.color(COLOR_SYNTAX).tx(" \"");
x.tx(s.getUrl());
x.color(COLOR_SYNTAX).tx("\" ");
if (s.hasAlias()) {
x.b().tx("alias ");
x.tx(s.getAlias());
x.tx(" ");
}
x.b().tx("as ");
x.b().tx(s.getMode().toCode());
renderDoco(x, s.getDocumentation(), false, null);
x.tx("\r\n");
}
if (map.hasStructure())
x.tx("\r\n");
}
private void renderImports(XhtmlNode x,StructureMap map) {
for (UriType s : map.getImport()) {
x.b().tx("imports");
x.color(COLOR_SYNTAX).tx(" \"");
x.tx(s.getValue());
x.color(COLOR_SYNTAX).tx("\"\r\n");
}
if (map.hasImport())
x.tx("\r\n");
}
private void renderGroup(XhtmlNode x,StructureMapGroupComponent g) {
Collection<String> tokens = scanVariables(g, null);
if (g.hasFormatCommentPre()) {
renderMultilineDoco(x, g.getFormatCommentsPre(), 0, tokens);
}
if (g.hasDocumentation()) {
renderMultilineDoco(x, g.getDocumentation(), 0, tokens);
}
x.b().tx("group ");
x.tx(g.getName());
x.color(COLOR_SYNTAX).tx("(");
boolean first = true;
for (StructureMapGroupInputComponent gi : g.getInput()) {
if (first)
first = false;
else
x.tx(", ");
x.b().tx(gi.getMode().toCode());
x.tx(" ");
x.color(COLOR_VARIABLE).tx(gi.getName());
if (gi.hasType()) {
x.color(COLOR_SYNTAX).tx(" : ");
x.tx(gi.getType());
}
}
x.color(COLOR_SYNTAX).tx(")");
if (g.hasExtends()) {
x.b().tx(" extends ");
x.tx(g.getExtends());
}
if (g.hasTypeMode()) {
switch (g.getTypeMode()) {
case TYPES:
x.b().tx(" <<types>>");
break;
case TYPEANDTYPES:
x.b().tx(" <<type+>>");
break;
default: // NONE, NULL
}
}
x.color(COLOR_SYNTAX).tx(" {\r\n");
for (StructureMapGroupRuleComponent r : g.getRule()) {
renderRule(x, g, r, 2);
}
if (g.hasFormatCommentPost()) {
renderMultilineDoco(x, g.getFormatCommentsPost(), 0, scanVariables(g, null));
}
x.color(COLOR_SYNTAX).tx("}\r\n\r\n");
}
private void renderRule(XhtmlNode x, StructureMapGroupComponent g, StructureMapGroupRuleComponent r, int indent) {
Collection<String> tokens = scanVariables(g, r);
if (r.hasFormatCommentPre()) {
renderMultilineDoco(x, r.getFormatCommentsPre(), indent, tokens);
}
for (int i = 0; i < indent; i++)
x.tx(" ");
boolean canBeAbbreviated = checkisSimple(r);
{
boolean first = true;
for (StructureMapGroupRuleSourceComponent rs : r.getSource()) {
if (first)
first = false;
else
x.color(COLOR_SYNTAX).tx(", ");
renderSource(x, rs, canBeAbbreviated);
}
}
if (r.getTarget().size() > 1) {
x.b().tx(" -> ");
boolean first = true;
for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) {
if (first)
first = false;
else
x.color(COLOR_SYNTAX).tx(", ");
if (RENDER_MULTIPLE_TARGETS_ONELINE)
x.tx(" ");
else {
x.tx("\r\n");
for (int i = 0; i < indent + 4; i++)
x.tx(" ");
}
renderTarget(x, rt, false);
}
} else if (r.hasTarget()) {
x.b().tx(" -> ");
renderTarget(x, r.getTarget().get(0), canBeAbbreviated);
}
if (r.hasRule()) {
x.b().tx(" then");
x.color(COLOR_SYNTAX).tx(" {\r\n");
for (StructureMapGroupRuleComponent ir : r.getRule()) {
renderRule(x, g, ir, indent + 2);
}
for (int i = 0; i < indent; i++)
x.tx(" ");
x.color(COLOR_SYNTAX).tx("}");
} else {
if (r.hasDependent()) {
x.b().tx(" then ");
boolean first = true;
for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) {
if (first)
first = false;
else
x.color(COLOR_SYNTAX).tx(", ");
x.tx(rd.getName());
x.color(COLOR_SYNTAX).tx("(");
boolean ifirst = true;
for (StructureMapGroupRuleTargetParameterComponent rdp : rd.getParameter()) {
if (ifirst)
ifirst = false;
else
x.color(COLOR_SYNTAX).tx(", ");
renderTransformParam(x, rdp);
}
x.color(COLOR_SYNTAX).tx(")");
}
}
}
if (r.hasName()) {
String n = ntail(r.getName());
if (!n.startsWith("\""))
n = "\"" + n + "\"";
if (!matchesName(n, r.getSource())) {
x.tx(" ");
x.i().tx(n);
}
}
x.color(COLOR_SYNTAX).tx(";");
if (r.hasDocumentation()) {
renderDoco(x, r.getDocumentation(), false, null);
}
x.tx("\r\n");
if (r.hasFormatCommentPost()) {
renderMultilineDoco(x, r.getFormatCommentsPost(), indent, tokens);
}
}
private Collection<String> scanVariables(StructureMapGroupComponent g, StructureMapGroupRuleComponent r) {
Set<String> res = new HashSet<>();
for (StructureMapGroupInputComponent input : g.getInput()) {
res.add(input.getName());
}
if (r != null) {
for (StructureMapGroupRuleSourceComponent src : r.getSource()) {
if (src.hasVariable()) {
res.add(src.getVariable());
}
}
}
return res;
}
private boolean matchesName(String n, List<StructureMapGroupRuleSourceComponent> source) {
if (source.size() != 1)
return false;
if (!source.get(0).hasElement())
return false;
String s = source.get(0).getElement();
if (n.equals(s) || n.equals("\"" + s + "\""))
return true;
if (source.get(0).hasType()) {
s = source.get(0).getElement() + "-" + source.get(0).getType();
return n.equals(s) || n.equals("\"" + s + "\"");
}
return false;
}
private String ntail(String name) {
if (name == null)
return null;
if (name.startsWith("\"")) {
name = name.substring(1);
name = name.substring(0, name.length() - 1);
}
return "\"" + (name.contains(".") ? name.substring(name.lastIndexOf(".") + 1) : name) + "\"";
}
private boolean checkisSimple(StructureMapGroupRuleComponent r) {
return
(r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) &&
(r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) &&
(r.getDependent().size() == 0) && (r.getRule().size() == 0);
}
private void renderSource(XhtmlNode x,StructureMapGroupRuleSourceComponent rs, boolean abbreviate) {
x.tx(rs.getContext());
if (rs.getContext().equals("@search")) {
x.color(COLOR_SYNTAX).tx("(");
x.tx(rs.getElement());
x.color(COLOR_SYNTAX).tx(")");
} else if (rs.hasElement()) {
x.tx(".");
x.tx(rs.getElement());
}
if (rs.hasType()) {
x.color(COLOR_SYNTAX).tx(" : ");
x.tx(rs.getType());
if (rs.hasMin()) {
x.tx(" ");
x.tx(rs.getMin());
x.color(COLOR_SYNTAX).tx("..");
x.tx(rs.getMax());
}
}
if (rs.hasListMode()) {
x.tx(" ");
x.tx(rs.getListMode().toCode());
}
if (rs.hasDefaultValue()) {
x.b().tx(" default ");
x.tx("\"" + Utilities.escapeJson(rs.getDefaultValue()) + "\"");
}
if (!abbreviate && rs.hasVariable()) {
x.b().tx(" as ");
x.color(COLOR_VARIABLE).tx(rs.getVariable());
}
if (rs.hasCondition()) {
x.b().tx(" where ");
x.tx(rs.getCondition());
}
if (rs.hasCheck()) {
x.b().tx(" check ");
x.tx(rs.getCheck());
}
if (rs.hasLogMessage()) {
x.b().tx(" log ");
x.tx(rs.getLogMessage());
}
}
private void renderTarget(XhtmlNode x,StructureMapGroupRuleTargetComponent rt, boolean abbreviate) {
if (rt.hasContext()) {
x.tx(rt.getContext());
if (rt.hasElement()) {
x.tx(".");
x.tx(rt.getElement());
}
}
if (!abbreviate && rt.hasTransform()) {
if (rt.hasContext())
x.tx(" = ");
if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) {
renderTransformParam(x, rt.getParameter().get(0));
} else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) {
x.color(COLOR_SYNTAX).tx("(");
x.tx(((StringType) rt.getParameter().get(0).getValue()).asStringValue());
x.color(COLOR_SYNTAX).tx(")");
} else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) {
x.tx(rt.getTransform().toCode());
x.color(COLOR_SYNTAX).tx("(");
x.tx(((IdType) rt.getParameter().get(0).getValue()).asStringValue());
x.color(COLOR_SYNTAX).tx(", ");
x.tx(((StringType) rt.getParameter().get(1).getValue()).asStringValue());
x.color(COLOR_SYNTAX).tx(")");
} else {
x.b().tx(rt.getTransform().toCode());
x.color(COLOR_SYNTAX).tx("(");
boolean first = true;
for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) {
if (first)
first = false;
else
x.color(COLOR_SYNTAX).tx(", ");
renderTransformParam(x, rtp);
}
x.color(COLOR_SYNTAX).tx(")");
}
}
if (!abbreviate && rt.hasVariable()) {
x.b().tx(" as ");
x.color(COLOR_VARIABLE).tx(rt.getVariable());
}
for (Enumeration<StructureMapTargetListMode> lm : rt.getListMode()) {
x.tx(" ");
x.b().tx(lm.getValue().toCode());
if (lm.getValue() == StructureMapTargetListMode.SHARE) {
x.tx(" ");
x.b().tx(rt.getListRuleId());
}
}
}
private void renderTransformParam(XhtmlNode x,StructureMapGroupRuleTargetParameterComponent rtp) {
try {
if (rtp.hasValueBooleanType())
x.color(COLOR_CONST).tx(rtp.getValueBooleanType().asStringValue());
else if (rtp.hasValueDecimalType())
x.color(COLOR_CONST).tx(rtp.getValueDecimalType().asStringValue());
else if (rtp.hasValueIdType())
x.color(COLOR_VARIABLE).tx(rtp.getValueIdType().asStringValue());
else if (rtp.hasValueIntegerType())
x.color(COLOR_CONST).tx(rtp.getValueIntegerType().asStringValue());
else
x.color(COLOR_CONST).tx("'" + Utilities.escapeJava(rtp.getValueStringType().asStringValue()) + "'");
} catch (FHIRException e) {
e.printStackTrace();
x.tx("error!");
}
}
private void renderDoco(XhtmlNode x,String doco, boolean startLine, Collection<String> tokens) {
if (Utilities.noString(doco))
return;
if (!startLine) {
x.tx(" ");
}
boolean isClause = false;
String t = doco.trim().replace(" ", "");
if (tokens != null) {
for (String s : tokens) {
if (t.startsWith(s+":") || t.startsWith(s+".") || t.startsWith(s+"->")) {
isClause = true;
break;
}
}
}
if (isClause) {
XhtmlNode s= x.color(COLOR_SPECIAL);
s.setAttribute("title", clauseComment );
s.tx("// ");
s.tx(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " "));
} else {
x.color(COLOR_SYNTAX).tx("// ");
x.color(COLOR_COMMENT).tx(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " "));
}
}
private void renderMultilineDoco(XhtmlNode x,String doco, int indent, Collection<String> tokens) {
if (Utilities.noString(doco))
return;
String[] lines = doco.split("\\r?\\n");
for (String line : lines) {
for (int i = 0; i < indent; i++)
x.tx(" ");
renderDoco(x, line, true, tokens);
x.tx("\r\n");
}
}
private void renderMultilineDoco(XhtmlNode x, List<String> doco, int indent, Collection<String> tokens) {
for (String line : doco) {
for (int i = 0; i < indent; i++)
x.tx(" ");
renderDoco(x, line, true, tokens);
x.tx("\r\n");
}
}
public void describe(XhtmlNode x, OperationDefinition opd) {
x.tx(display(opd));
}
public String display(OperationDefinition opd) {
return opd.present();
}
@Override
public String display(Resource r) throws UnsupportedEncodingException, IOException {
return ((StructureMap) r).present();
}
}

View File

@ -315,18 +315,23 @@ public class FHIRLexer {
private void skipWhitespaceAndComments() {
comments.clear();
commentLocation = null;
boolean last13 = false;
boolean done = false;
while (cursor < source.length() && !done) {
if (cursor < source.length() -1 && "//".equals(source.substring(cursor, cursor+2)) && !isMetadataStart()) {
commentLocation = currentLocation;
if (commentLocation == null) {
commentLocation = currentLocation.copy();
}
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;
if (commentLocation == null) {
commentLocation = currentLocation.copy();
}
int start = cursor+2;
while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor+2))) {
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
@ -569,5 +574,19 @@ public class FHIRLexer {
public void setMetadataFormat(boolean metadataFormat) {
this.metadataFormat = metadataFormat;
}
public List<String> cloneComments() {
List<String> res = new ArrayList<>();
res.addAll(getComments());
return res;
}
public String tokenWithTrailingComment(String token) {
int line = getCurrentLocation().getLine();
token(token);
if (getComments().size() > 0 && getCommentLocation().getLine() == line) {
return getFirstComment();
} else {
return null;
}
}
}

View File

@ -71,6 +71,7 @@ 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.VersionUtilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
@ -113,6 +114,7 @@ public class StructureMapUtilities {
private final Map<String, Integer> ids = new HashMap<String, Integer>();
private ValidationOptions terminologyServiceOptions = new ValidationOptions();
private final ProfileUtilities profileUtilities;
private boolean exceptionsForChecks = true;
public StructureMapUtilities(IWorkerContext worker, ITransformerServices services, ProfileKnowledgeProvider pkp) {
super();
@ -341,8 +343,8 @@ public class StructureMapUtilities {
}
private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) {
if (r.getDocumentation() != null) {
renderMultilineDoco(b, r.getDocumentation(), indent);
if (r.hasFormatCommentPre()) {
renderMultilineDoco(b, r.getFormatCommentsPre(), indent);
}
for (int i = 0; i < indent; i++)
b.append(' ');
@ -610,6 +612,16 @@ public class StructureMapUtilities {
b.append("\r\n");
}
}
private static void renderMultilineDoco(StringBuilder b, List<String> doco, int indent) {
if (doco == null || doco.isEmpty())
return;
for (String line : doco) {
for (int i = 0; i < indent; i++)
b.append(' ');
renderDoco(b, line);
b.append("\r\n");
}
}
public ITransformerServices getServices() {
return services;
@ -672,8 +684,9 @@ public class StructureMapUtilities {
private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException {
lexer.token("conceptmap");
ConceptMap map = new ConceptMap();
map.addFormatCommentsPre(lexer.getComments());
lexer.token("conceptmap");
String id = lexer.readConstant("map id");
if (id.startsWith("#"))
throw lexer.error("Concept Map identifier must start with #");
@ -694,10 +707,12 @@ public class StructureMapUtilities {
prefixes.put(n, v);
}
while (lexer.hasToken("unmapped")) {
List<String> comments = lexer.cloneComments();
lexer.token("unmapped");
lexer.token("for");
String n = readPrefix(prefixes, lexer);
ConceptMapGroupComponent g = getGroup(map, n, null);
g.addFormatCommentsPre(comments);
lexer.token("=");
String v = lexer.take();
if (v.equals("provided")) {
@ -706,6 +721,7 @@ public class StructureMapUtilities {
throw lexer.error("Only unmapped mode PROVIDED is supported at this time");
}
while (!lexer.hasToken("}")) {
List<String> comments = lexer.cloneComments();
String srcs = readPrefix(prefixes, lexer);
lexer.token(":");
String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take();
@ -713,17 +729,21 @@ public class StructureMapUtilities {
String tgts = readPrefix(prefixes, lexer);
ConceptMapGroupComponent g = getGroup(map, srcs, tgts);
SourceElementComponent e = g.addElement();
e.addFormatCommentsPre(comments);
e.setCode(sc);
if (e.getCode().startsWith("\""))
if (e.getCode().startsWith("\"")) {
e.setCode(lexer.processConstant(e.getCode()));
}
TargetElementComponent tgt = e.addTarget();
tgt.setRelationship(rel);
lexer.token(":");
tgt.setCode(lexer.take());
if (tgt.getCode().startsWith("\""))
if (tgt.getCode().startsWith("\"")) {
tgt.setCode(lexer.processConstant(tgt.getCode()));
tgt.setComment(lexer.getFirstComment());
}
// tgt.setComment(lexer.getAllComments());
}
map.addFormatCommentsPost(lexer.getComments());
lexer.token("}");
}
@ -779,9 +799,20 @@ public class StructureMapUtilities {
st.setAlias(lexer.take());
}
lexer.token("as");
st.setMode(StructureMapModelMode.fromCode(lexer.take()));
lexer.skipToken(";");
st.setDocumentation(lexer.getFirstComment());
String doco;
if (lexer.getCurrent().equals("source")) {
st.setMode(StructureMapModelMode.SOURCE);
doco = lexer.tokenWithTrailingComment("source");
} else if (lexer.getCurrent().equals("target")) {
st.setMode(StructureMapModelMode.TARGET);
doco = lexer.tokenWithTrailingComment("target");
} else {
throw lexer.error("Found '"+lexer.getCurrent()+"' expecting 'source' or 'target'");
}
if (lexer.hasToken(";")) {
doco = lexer.tokenWithTrailingComment(";");
}
st.setDocumentation(doco);
}
@ -859,6 +890,7 @@ public class StructureMapUtilities {
parseRule(result, group.getRule(), lexer, false);
}
}
group.addFormatCommentsPost(lexer.getComments());
lexer.next();
if (newFmt && lexer.hasToken(";"))
lexer.next();
@ -880,7 +912,7 @@ public class StructureMapUtilities {
if (!newFmt) {
lexer.token("as");
input.setMode(StructureMapInputMode.fromCode(lexer.take()));
input.setDocumentation(lexer.getFirstComment());
input.setDocumentation(lexer.getAllComments());
lexer.skipToken(";");
}
}
@ -894,7 +926,7 @@ public class StructureMapUtilities {
lexer.token(":");
lexer.token("for");
} else {
rule.setDocumentation(lexer.getFirstComment());
rule.addFormatCommentsPre(lexer.getComments());
}
list.add(rule);
boolean done = false;
@ -934,9 +966,6 @@ public class StructureMapUtilities {
}
}
}
if (!rule.hasDocumentation() && lexer.hasComments()) {
rule.setDocumentation(lexer.getFirstComment());
}
if (isSimpleSyntax(rule)) {
rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME);
rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME);
@ -951,14 +980,17 @@ public class StructureMapUtilities {
rule.setName(lexer.take());
}
} else {
if (rule.getSource().size() != 1 || !rule.getSourceFirstRep().hasElement())
if (rule.getSource().size() != 1 || !rule.getSourceFirstRep().hasElement() && exceptionsForChecks )
throw lexer.error("Complex rules must have an explicit name");
if (rule.getSourceFirstRep().hasType())
rule.setName(rule.getSourceFirstRep().getElement() + "-" + rule.getSourceFirstRep().getType());
else
rule.setName(rule.getSourceFirstRep().getElement());
}
lexer.token(";");
String doco = lexer.tokenWithTrailingComment(";");
if (doco != null) {
rule.setDocumentation(doco);
}
}
}
@ -2640,4 +2672,12 @@ public class StructureMapUtilities {
this.terminologyServiceOptions = terminologyServiceOptions;
}
public boolean isExceptionsForChecks() {
return exceptionsForChecks;
}
public void setExceptionsForChecks(boolean exceptionsForChecks) {
this.exceptionsForChecks = exceptionsForChecks;
}
}

View File

@ -34,6 +34,7 @@ import org.hl7.fhir.r5.renderers.utils.RenderingContext.StructureDefinitionRende
import org.hl7.fhir.r5.test.utils.CompareUtilities;
import org.hl7.fhir.r5.test.utils.TestPackageLoader;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
@ -153,6 +154,7 @@ public class NarrativeGenerationTests {
private String id;
private String sdmode;
private boolean header;
private boolean pretty;
private boolean meta;
private boolean technical;
private String register;
@ -169,6 +171,7 @@ public class NarrativeGenerationTests {
register = null;
}
header = "true".equals(test.getAttribute("header"));
pretty = !"false".equals(test.getAttribute("pretty"));
meta = "true".equals(test.getAttribute("meta"));
technical = "technical".equals(test.getAttribute("mode"));
}
@ -247,13 +250,15 @@ public class NarrativeGenerationTests {
Resource source;
if (TestingUtilities.findTestResource("r5", "narrative", test.getId() + ".json")) {
source = (Resource) new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".json"));
} else if (TestingUtilities.findTestResource("r5", "narrative", test.getId() + ".fml")) {
source = (Resource) new StructureMapUtilities(context).parse(TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".fml")), "source");
} else {
source = (Resource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".xml"));
}
XhtmlNode x = RendererFactory.factory(source, rc).build(source);
String expected = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".html"));
String actual = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER;
String actual = HEADER+new XhtmlComposer(true, test.pretty).compose(x)+FOOTER;
String expectedFileName = CompareUtilities.tempFile("narrative", test.getId() + ".expected.html");
String actualFileName = CompareUtilities.tempFile("narrative", test.getId() + ".actual.html");
TextFile.stringToFile(expected, expectedFileName);

View File

@ -69,8 +69,8 @@ public class StructureMapUtilitiesTest implements ITransformerServices {
Assertions.assertEquals("http://hl7.org/fhir/StructureDefinition/Basic", structureMap.getStructure().get(1).getUrl());
Assertions.assertEquals("Target Documentation", structureMap.getStructure().get(1).getDocumentation());
Assertions.assertEquals("Groups\r\nrule for patient group", structureMap.getGroup().get(0).getDocumentation());
Assertions.assertEquals("Comment to rule", structureMap.getGroup().get(0).getRule().get(0).getDocumentation());
Assertions.assertEquals("Copy identifier short syntax", structureMap.getGroup().get(0).getRule().get(1).getDocumentation());
Assertions.assertEquals("Comment to rule", structureMap.getGroup().get(0).getRule().get(0).getFormatCommentsPre().get(0));
Assertions.assertEquals("Copy identifier short syntax", structureMap.getGroup().get(0).getRule().get(1).getFormatCommentsPre().get(0));
StructureMapGroupRuleTargetComponent target = structureMap.getGroup().get(0).getRule().get(2).getTarget().get(1);
Assertions.assertEquals("'urn:uuid:' + r.lower()", target.getParameter().get(0).toString());
@ -80,14 +80,12 @@ public class StructureMapUtilitiesTest implements ITransformerServices {
public void testSyntax() throws IOException, FHIRException {
StructureMapUtilities scu = new StructureMapUtilities(context, this);
String fileMap = TestingUtilities.loadTestResource("r5", "structure-mapping", "syntax.map");
System.out.println(fileMap);
StructureMap structureMap = scu.parse(fileMap, "Syntax");
assertSerializeDeserialize(structureMap);
String renderedMap = StructureMapUtilities.render(structureMap);
StructureMap map = scu.parse(renderedMap, "Syntax");
System.out.println(map);
assertSerializeDeserialize(map);
}

View File

@ -43,4 +43,7 @@ public class SourceLocation {
return false;
}
}
public SourceLocation copy() {
return new SourceLocation(line, column);
}
}

View File

@ -765,6 +765,10 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
this.getChildNodes().addAll(childNodes);
}
public XhtmlNode color(String color) {
return span("color: "+color, null);
}
}