diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpanderSimple.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpanderSimple.java index 0b33ecbc3..48f1f5ad7 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpanderSimple.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpanderSimple.java @@ -68,6 +68,7 @@ import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.NoTerminologyServiceException; import org.hl7.fhir.exceptions.TerminologyServiceException; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.BooleanType; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; @@ -319,7 +320,7 @@ public class ValueSetExpanderSimple implements ValueSetExpander { } if (source.hasCompose()) - handleCompose(source.getCompose(), focus.getExpansion().getParameter(), expParams, source.getUrl(), focus.getExpansion().getExtension()); + handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension()); if (canBeHeirarchy) { for (ValueSetExpansionContainsComponent c : roots) { @@ -372,12 +373,12 @@ public class ValueSetExpanderSimple implements ValueSetExpander { return null; } - private void handleCompose(ValueSetComposeComponent compose, List params, Parameters expParams, String ctxt, List extensions) + private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List extensions) throws ETooCostly, FileNotFoundException, IOException, FHIRException { compose.checkNoModifiers("ValueSet.compose", "expanding"); // Exclude comes first because we build up a map of things to exclude for (ConceptSetComponent inc : compose.getExclude()) - excludeCodes(inc, params, ctxt); + excludeCodes(inc, exp.getParameter(), ctxt); canBeHeirarchy = !expParams.getParameterBool("excludeNested") && excludeKeys.isEmpty() && excludeSystems.isEmpty(); boolean first = true; for (ConceptSetComponent inc : compose.getInclude()) { @@ -385,12 +386,12 @@ public class ValueSetExpanderSimple implements ValueSetExpander { first = false; else canBeHeirarchy = false; - includeCodes(inc, params, expParams, canBeHeirarchy, extensions); + includeCodes(inc, exp, expParams, canBeHeirarchy, extensions); } } - private ValueSet importValueSet(String value, List params, Parameters expParams) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError { + private ValueSet importValueSet(String value, ValueSetExpansionComponent exp, Parameters expParams) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError { if (value == null) throw new TerminologyServiceException("unable to find value set with no identity"); ValueSet vs = context.fetchResource(ValueSet.class, value); @@ -400,11 +401,20 @@ public class ValueSetExpanderSimple implements ValueSetExpander { if (vso.getError() != null) throw new TerminologyServiceException("Unable to expand imported value set: " + vso.getError()); if (vs.hasVersion()) - if (!existsInParams(params, "version", new UriType(vs.getUrl() + "|" + vs.getVersion()))) - params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl() + "|" + vs.getVersion()))); + if (!existsInParams(exp.getParameter(), "version", new UriType(vs.getUrl() + "|" + vs.getVersion()))) + exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl() + "|" + vs.getVersion()))); + for (Extension ex : vso.getValueset().getExpansion().getExtension()) { + if (ex.getUrl().equals("http://hl7.org/fhir/StructureDefinition/valueset-toocostly")) { + if (ex.getValue() instanceof BooleanType) { + exp.getExtension().add(new Extension("http://hl7.org/fhir/StructureDefinition/valueset-toocostly").setValue(new UriType(value))); + } else { + exp.getExtension().add(ex); + } + } + } for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { - if (!existsInParams(params, p.getName(), p.getValue())) - params.add(p); + if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) + exp.getParameter().add(p); } canBeHeirarchy = false; // if we're importing a value set, we have to be combining, so we won't try for a heirarchy return vso.getValueset(); @@ -418,11 +428,11 @@ public class ValueSetExpanderSimple implements ValueSetExpander { } } - private void includeCodes(ConceptSetComponent inc, List params, Parameters expParams, boolean heirarchical, List extensions) throws ETooCostly, FileNotFoundException, IOException, FHIRException { + private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, List extensions) throws ETooCostly, FileNotFoundException, IOException, FHIRException { inc.checkNoModifiers("Compose.include", "expanding"); List imports = new ArrayList(); for (UriType imp : inc.getValueSet()) { - imports.add(importValueSet(imp.getValue(), params, expParams)); + imports.add(importValueSet(imp.getValue(), exp, expParams)); } if (!inc.hasSystem()) { @@ -435,27 +445,27 @@ public class ValueSetExpanderSimple implements ValueSetExpander { } else { CodeSystem cs = context.fetchCodeSystem(inc.getSystem()); if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE)) { - doServerIncludeCodes(inc, heirarchical, params, imports, expParams, extensions); + doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions); } else { - doInternalIncludeCodes(inc, params, expParams, imports, cs); + doInternalIncludeCodes(inc, exp, expParams, imports, cs); } } } - private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, List params, List imports, Parameters expParams, List extensions) throws FHIRException { + private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, List imports, Parameters expParams, List extensions) throws FHIRException { ValueSetExpansionOutcome vso = context.expandVS(inc, heirarchical); if (vso.getError() != null) { throw new TerminologyServiceException("Unable to expand imported value set: " + vso.getError()); } ValueSet vs = vso.getValueset(); if (vs.hasVersion()) { - if (!existsInParams(params, "version", new UriType(vs.getUrl() + "|" + vs.getVersion()))) { - params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl() + "|" + vs.getVersion()))); + if (!existsInParams(exp.getParameter(), "version", new UriType(vs.getUrl() + "|" + vs.getVersion()))) { + exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(vs.getUrl() + "|" + vs.getVersion()))); } } for (ValueSetExpansionParameterComponent p : vso.getValueset().getExpansion().getParameter()) { - if (!existsInParams(params, p.getName(), p.getValue())) { - params.add(p); + if (!existsInParams(exp.getParameter(), p.getName(), p.getValue())) { + exp.getParameter().add(p); } } for (Extension ex : vs.getExpansion().getExtension()) { @@ -479,7 +489,7 @@ public class ValueSetExpanderSimple implements ValueSetExpander { return false; } - public void doInternalIncludeCodes(ConceptSetComponent inc, List params, Parameters expParams, List imports, + public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List imports, CodeSystem cs) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException { if (cs == null) { if (context.isNoTerminologyServer()) @@ -491,8 +501,8 @@ public class ValueSetExpanderSimple implements ValueSetExpander { if (cs.getContent() != CodeSystemContentMode.COMPLETE) throw new TerminologyServiceException("Code system " + inc.getSystem().toString() + " is incomplete"); if (cs.hasVersion()) - if (!existsInParams(params, "version", new UriType(cs.getUrl() + "|" + cs.getVersion()))) - params.add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(cs.getUrl() + "|" + cs.getVersion()))); + if (!existsInParams(exp.getParameter(), "version", new UriType(cs.getUrl() + "|" + cs.getVersion()))) + exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("version").setValue(new UriType(cs.getUrl() + "|" + cs.getVersion()))); if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { // special case - add all the code system diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NarrativeGenerator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NarrativeGenerator.java index 34249693f..e13c884a9 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NarrativeGenerator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NarrativeGenerator.java @@ -422,7 +422,7 @@ public class NarrativeGenerator implements INarrativeGenerator { throw new FHIRException("Error in template: Root element is not 'div'"); } catch (FHIRException | IOException e) { x = new XhtmlNode(NodeType.Element, "div"); - x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage()); + x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); } inject(r, x, NarrativeStatus.GENERATED); return true; @@ -1109,6 +1109,8 @@ public class NarrativeGenerator implements INarrativeGenerator { private String basePath; private String tooCostlyNoteEmpty; private String tooCostlyNoteNotEmpty; + private String tooCostlyNoteEmptyDependent; + private String tooCostlyNoteNotEmptyDependent; private IReferenceResolver resolver; private int headerLevelContext; private List renderingMaps = new ArrayList(); @@ -1190,6 +1192,22 @@ public class NarrativeGenerator implements INarrativeGenerator { } + public String getTooCostlyNoteEmptyDependent() { + return tooCostlyNoteEmptyDependent; + } + + public void setTooCostlyNoteEmptyDependent(String tooCostlyNoteEmptyDependent) { + this.tooCostlyNoteEmptyDependent = tooCostlyNoteEmptyDependent; + } + + public String getTooCostlyNoteNotEmptyDependent() { + return tooCostlyNoteNotEmptyDependent; + } + + public void setTooCostlyNoteNotEmptyDependent(String tooCostlyNoteNotEmptyDependent) { + this.tooCostlyNoteNotEmptyDependent = tooCostlyNoteNotEmptyDependent; + } + // dom based version, for build program public String generate(Element doc) throws IOException, org.hl7.fhir.exceptions.FHIRException { return generate(null, doc); @@ -1225,7 +1243,7 @@ public class NarrativeGenerator implements INarrativeGenerator { } catch (Exception e) { e.printStackTrace(); - x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage()); + x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); } } inject(er, x, NarrativeStatus.GENERATED); @@ -1239,7 +1257,7 @@ public class NarrativeGenerator implements INarrativeGenerator { generateByProfile(rc.resourceResource, profile, rc.resourceResource, profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile.getSnapshot().getElement(), rc.resourceResource.getResourceType().toString()), x, rc.resourceResource.getResourceType().toString(), showCodeDetails, rc); } catch (Exception e) { e.printStackTrace(); - x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage()); + x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); } inject(rc.resourceResource, x, NarrativeStatus.GENERATED); return true; @@ -1252,7 +1270,7 @@ public class NarrativeGenerator implements INarrativeGenerator { generateByProfile(er, profile, er, profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile.getSnapshot().getElement(), er.getLocalName()), x, er.getLocalName(), showCodeDetails); } catch (Exception e) { e.printStackTrace(); - x.para().b().setAttribute("style", "color: maroon").tx("Exception generating Narrative: "+e.getMessage()); + x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); } inject(er, x, NarrativeStatus.GENERATED); String b = new XhtmlComposer(XhtmlComposer.XML, pretty).compose(x); @@ -3240,9 +3258,18 @@ public class NarrativeGenerator implements INarrativeGenerator { if (vs.hasCopyright()) generateCopyright(x, vs); } - if (ToolingExtensions.hasExtension(vs.getExpansion(), "http://hl7.org/fhir/StructureDefinition/valueset-toocostly")) - x.para().setAttribute("style", "border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(vs.getExpansion().getContains().isEmpty() ? tooCostlyNoteEmpty : tooCostlyNoteNotEmpty ); - else { + if (ToolingExtensions.hasExtension(vs.getExpansion(), "http://hl7.org/fhir/StructureDefinition/valueset-toocostly")) { + List exl = vs.getExpansion().getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/valueset-toocostly"); + boolean other = false; + for (Extension ex : exl) { + if (ex.getValue() instanceof BooleanType) { + x.para().style("border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(vs.getExpansion().getContains().isEmpty() ? tooCostlyNoteEmpty : tooCostlyNoteNotEmpty ); + } else if (!other) { + x.para().style("border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(vs.getExpansion().getContains().isEmpty() ? tooCostlyNoteEmptyDependent : tooCostlyNoteNotEmptyDependent ); + other = true; + } + } + } else { Integer count = countMembership(vs); if (count == null) x.para().tx("This value set does not contain a fixed number of concepts"); @@ -3313,48 +3340,117 @@ public class NarrativeGenerator implements INarrativeGenerator { @SuppressWarnings("rawtypes") private void generateVersionNotice(XhtmlNode x, ValueSetExpansionComponent expansion) { Map versions = new HashMap(); - boolean firstVersion = true; for (ValueSetExpansionParameterComponent p : expansion.getParameter()) { if (p.getName().equals("version")) { String[] parts = ((PrimitiveType) p.getValue()).asStringValue().split("\\|"); if (parts.length == 2) versions.put(parts[0], parts[1]); - if (!versions.isEmpty()) { - StringBuilder b = new StringBuilder(); - if (firstVersion) { - // the first version - // set the

tag and style attribute - x.para().setAttribute("style", "border: black 1px dotted; background-color: #EEEEEE; padding: 8px"); - firstVersion = false; - } else { - // the second (or greater) version - x.br(); // add line break before the version text - } - b.append("Expansion based on "); - boolean firstPart = true; - for (String s : versions.keySet()) { - if (firstPart) - firstPart = false; - else - b.append(", "); - if (!s.equals("http://snomed.info/sct")) - b.append(describeSystem(s)+" version "+versions.get(s)); - else { - parts = versions.get(s).split("\\/"); - if (parts.length >= 5) { - String m = describeModule(parts[4]); - if (parts.length == 7) - b.append("SNOMED CT "+m+" edition "+formatSCTDate(parts[6])); - else - b.append("SNOMED CT "+m+" edition"); - } else - b.append(describeSystem(s)+" version "+versions.get(s)); - } - } - x.addText(b.toString()); // add the version text - } } } + if (versions.size() > 1) { + XhtmlNode div = x.div().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px"); + div.para().tx("Expansion based on: "); + XhtmlNode ul = div.ul(); + for (String s : versions.keySet()) { // though there'll only be one + expRef(ul.li(), s, versions.get(s)); + } + } else if (versions.size() == 1) { + XhtmlNode p = x.para().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px"); + p.tx("Expansion based on "); + for (String s : versions.keySet()) { // though there'll only be one + expRef(p, s, versions.get(s)); + } + } + } + + private void expRef(XhtmlNode x, String u, String v) { + // TODO Auto-generated method stub + if (u.equals("http://snomed.info/sct")) { + String[] parts = v.split("\\/"); + if (parts.length >= 5) { + String m = describeModule(parts[4]); + if (parts.length == 7) { + x.tx("SNOMED CT "+m+" edition "+formatSCTDate(parts[6])); + } else { + x.tx("SNOMED CT "+m+" edition"); + } + } else { + x.tx(describeSystem(u)+" version "+v); + } + } else if (u.equals("http://loinc.org")) { + String vd = describeLoincVer(v); + if (vd != null) { + x.tx("Loinc v"+v+" ("+vd+")"); + } else { + x.tx("Loinc v"+v); + } + } else { + CanonicalResource cr = (CanonicalResource) context.fetchResource(Resource.class, u+"|"+v); + if (cr != null) { + if (cr.hasUserData("path")) { + x.ah(cr.getUserString("path")).tx(cr.present()+" v"+v+" ("+cr.fhirType()+")"); + } else { + x.tx(describeSystem(u)+" v"+v+" ("+cr.fhirType()+")"); + } + } else { + x.tx(describeSystem(u)+" version "+v); + } + } + } + + private String describeLoincVer(String v) { + if ("2.67".equals(v)) return "Dec 2019"; + if ("2.66".equals(v)) return "Jun 2019"; + if ("2.65".equals(v)) return "Dec 2018"; + if ("2.64".equals(v)) return "Jun 2018"; + if ("2.63".equals(v)) return "Dec 2017"; + if ("2.61".equals(v)) return "Jun 2017"; + if ("2.59".equals(v)) return "Feb 2017"; + if ("2.58".equals(v)) return "Dec 2016"; + if ("2.56".equals(v)) return "Jun 2016"; + if ("2.54".equals(v)) return "Dec 2015"; + if ("2.52".equals(v)) return "Jun 2015"; + if ("2.50".equals(v)) return "Dec 2014"; + if ("2.48".equals(v)) return "Jun 2014"; + if ("2.46".equals(v)) return "Dec 2013"; + if ("2.44".equals(v)) return "Jun 2013"; + if ("2.42".equals(v)) return "Dec 2012"; + if ("2.40".equals(v)) return "Jun 2012"; + if ("2.38".equals(v)) return "Dec 2011"; + if ("2.36".equals(v)) return "Jun 2011"; + if ("2.34".equals(v)) return "Dec 2010"; + if ("2.32".equals(v)) return "Jun 2010"; + if ("2.30".equals(v)) return "Feb 2010"; + if ("2.29".equals(v)) return "Dec 2009"; + if ("2.27".equals(v)) return "Jul 2009"; + if ("2.26".equals(v)) return "Jan 2009"; + if ("2.24".equals(v)) return "Jul 2008"; + if ("2.22".equals(v)) return "Dec 2007"; + if ("2.21".equals(v)) return "Jun 2007"; + if ("2.19".equals(v)) return "Dec 2006"; + if ("2.17".equals(v)) return "Jun 2006"; + if ("2.16".equals(v)) return "Dec 2005"; + if ("2.15".equals(v)) return "Jun 2005"; + if ("2.14".equals(v)) return "Dec 2004"; + if ("2.13".equals(v)) return "Aug 2004"; + if ("2.12".equals(v)) return "Feb 2004"; + if ("2.10".equals(v)) return "Oct 2003"; + if ("2.09".equals(v)) return "May 2003"; + if ("2.08 ".equals(v)) return "Sep 2002"; + if ("2.07".equals(v)) return "Aug 2002"; + if ("2.05".equals(v)) return "Feb 2002"; + if ("2.04".equals(v)) return "Jan 2002"; + if ("2.03".equals(v)) return "Jul 2001"; + if ("2.02".equals(v)) return "May 2001"; + if ("2.01".equals(v)) return "Jan 2001"; + if ("2.00".equals(v)) return "Jan 2001"; + if ("1.0n".equals(v)) return "Feb 2000"; + if ("1.0ma".equals(v)) return "Aug 1999"; + if ("1.0m".equals(v)) return "Jul 1999"; + if ("1.0l".equals(v)) return "Jan 1998"; + if ("1.0ja".equals(v)) return "Oct 1997"; + + return null; } private String formatSCTDate(String ds) { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java index 5b2d418ce..f43172434 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java @@ -526,6 +526,10 @@ public class XhtmlNode implements IBaseXhtml { return setAttribute("colspan", n); } + public XhtmlNode div() { + return addTag("div"); + } + public XhtmlNode para() { return addTag("p"); }