Merge branch 'master' of https://github.com/hapifhir/org.hl7.fhir.core into testing_updates

This commit is contained in:
markiantorno 2020-04-23 10:51:49 -04:00
commit 7673400a13
32 changed files with 2967 additions and 2337 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.18-SNAPSHOT</version>
<version>4.2.19-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -2,12 +2,12 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.18-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.19-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>org.hl7.fhir.dstu2</artifactId>
<packaging>bundle</packaging>

View File

@ -2,12 +2,12 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.18-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.19-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>org.hl7.fhir.dstu2016may</artifactId>
<packaging>bundle</packaging>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.18-SNAPSHOT</version>
<version>4.2.19-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.18-SNAPSHOT</version>
<version>4.2.19-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.18-SNAPSHOT</version>
<version>4.2.19-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -87,6 +87,7 @@ import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.terminologies.TerminologyRenderer;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.utils.NarrativeGenerator;
import org.hl7.fhir.r5.utils.ToolingExtensions;
@ -5467,7 +5468,7 @@ public class ProfileUtilities extends TranslatingUtilities {
private String summarizeCoding(Coding value) {
String uri = value.getSystem();
String system = NarrativeGenerator.describeSystem(uri);
String system = TerminologyRenderer.describeSystem(uri);
if (Utilities.isURL(system)) {
if (system.equals("http://cap.org/protocols"))
system = "CAP Code";

View File

@ -103,6 +103,7 @@ public class TerminologyCache {
private Object lock;
private String folder;
private Map<String, NamedCache> caches = new HashMap<String, NamedCache>();
private static boolean noCaching;
// use lock from the context
public TerminologyCache(Object lock, String folder) throws FileNotFoundException, IOException, FHIRException {
@ -245,6 +246,9 @@ public class TerminologyCache {
}
public void store(CacheToken cacheToken, boolean persistent, NamedCache nc, CacheEntry e) {
if (noCaching) {
return;
}
boolean n = nc.map.containsKey(cacheToken.key);
nc.map.put(cacheToken.key, e);
if (persistent) {
@ -426,7 +430,6 @@ public class TerminologyCache {
return code.getSystem()+"#"+code.getCode()+": \""+code.getDisplay()+"\"";
}
public String summary(CodeableConcept code) {
StringBuilder b = new StringBuilder();
b.append("{");
@ -440,5 +443,14 @@ public class TerminologyCache {
b.append("\"");
return b.toString();
}
public static boolean isNoCaching() {
return noCaching;
}
public static void setNoCaching(boolean noCaching) {
TerminologyCache.noCaching = noCaching;
}
}

View File

@ -0,0 +1,511 @@
package org.hl7.fhir.r5.terminologies;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.Enumeration;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode;
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.CodeSystemNavigator;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class CodeSystemRenderer extends TerminologyRenderer {
private List<String> codeSystemPropList = new ArrayList<>();
/**
*
* @param mode - whether we are rendering for a resource directly, or for an IG
* @param context - common services (terminology server, code system cache etc)
* @param markdown - markdown processing engine of the correct sort for the version applicable
* @param prefix - the path to the base FHIR specification
* @param lang - the language to use (null for the default)
*/
public CodeSystemRenderer(TerminologyRendererMode mode, IWorkerContext context, MarkDownProcessor markdown, String prefix, String lang) {
super(mode, context, markdown, prefix, lang);
}
public List<String> getCodeSystemPropList() {
return codeSystemPropList;
}
public boolean generate(XhtmlNode x, CodeSystem cs, boolean header) throws FHIRFormatError, DefinitionException, IOException {
boolean hasExtensions = false;
if (header) {
XhtmlNode h = x.h2();
h.addText(cs.hasTitle() ? cs.getTitle() : cs.getName());
addMarkdown(x, cs.getDescription());
if (cs.hasCopyright())
generateCopyright(x, cs);
}
generateProperties(x, cs);
generateFilters(x, cs);
List<UsedConceptMap> maps = new ArrayList<UsedConceptMap>();
hasExtensions = generateCodeSystemContent(x, cs, hasExtensions, maps);
return hasExtensions;
}
private void generateFilters(XhtmlNode x, CodeSystem cs) {
if (cs.hasFilter()) {
x.para().b().tx(context.translator().translate("xhtml-gen-cs", "Filters", lang));
XhtmlNode tbl = x.table("grid");
XhtmlNode tr = tbl.tr();
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Code", lang));
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Description", lang));
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "operator", lang));
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Value", lang));
for (CodeSystemFilterComponent f : cs.getFilter()) {
tr = tbl.tr();
tr.td().tx(f.getCode());
tr.td().tx(f.getDescription());
XhtmlNode td = tr.td();
for (Enumeration<org.hl7.fhir.r5.model.Enumerations.FilterOperator> t : f.getOperator())
td.tx(t.asStringValue()+" ");
tr.td().tx(f.getValue());
}
}
}
private void generateProperties(XhtmlNode x, CodeSystem cs) {
if (cs.hasProperty()) {
boolean hasRendered = false;
for (PropertyComponent p : cs.getProperty()) {
hasRendered = hasRendered || !p.getCode().equals(ToolingExtensions.getPresentation(p, p.getCodeElement()));
}
x.para().b().tx(context.translator().translate("xhtml-gen-cs", "Properties", lang));
XhtmlNode tbl = x.table("grid");
XhtmlNode tr = tbl.tr();
if (hasRendered) {
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Name", lang));
}
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Code", lang));
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "URL", lang));
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Description", lang));
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Type", lang));
for (PropertyComponent p : cs.getProperty()) {
tr = tbl.tr();
if (hasRendered) {
tr.td().tx(ToolingExtensions.getPresentation(p, p.getCodeElement()));
}
tr.td().tx(p.getCode());
tr.td().tx(p.getUri());
tr.td().tx(p.getDescription());
tr.td().tx(p.hasType() ? p.getType().toCode() : "");
}
}
}
private boolean generateCodeSystemContent(XhtmlNode x, CodeSystem cs, boolean hasExtensions, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException {
XhtmlNode p = x.para();
if (cs.getContent() == CodeSystemContentMode.COMPLETE)
p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines the following codes", cs.getUrl())+":");
else if (cs.getContent() == CodeSystemContentMode.EXAMPLE)
p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines many codes, of which the following are some examples", cs.getUrl())+":");
else if (cs.getContent() == CodeSystemContentMode.FRAGMENT )
p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines many codes, of which the following are a subset", cs.getUrl())+":");
else if (cs.getContent() == CodeSystemContentMode.NOTPRESENT ) {
p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines many codes, but they are not represented here", cs.getUrl()));
return false;
}
XhtmlNode t = x.table( "codes");
boolean commentS = false;
boolean deprecated = false;
boolean display = false;
boolean hierarchy = false;
boolean version = false;
List<PropertyComponent> properties = new ArrayList<>();
for (PropertyComponent cp : cs.getProperty()) {
if (showPropertyInTable(cp)) {
boolean exists = false;
for (ConceptDefinitionComponent c : cs.getConcept()) {
exists = exists || conceptsHaveProperty(c, cp);
}
if (exists) {
properties.add(cp);
}
}
}
for (ConceptDefinitionComponent c : cs.getConcept()) {
commentS = commentS || conceptsHaveComments(c);
deprecated = deprecated || conceptsHaveDeprecated(cs, c);
display = display || conceptsHaveDisplay(c);
version = version || conceptsHaveVersion(c);
hierarchy = hierarchy || c.hasConcept();
}
CodeSystemNavigator csNav = new CodeSystemNavigator(cs);
hierarchy = hierarchy || csNav.isRestructure();
List<String> langs = new ArrayList<>();
addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, true, commentS, version, deprecated, properties), maps);
for (ConceptDefinitionComponent c : csNav.getConcepts(null)) {
hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, commentS, version, deprecated, maps, cs.getUrl(), cs, properties, csNav, langs) || hasExtensions;
}
if (langs.size() > 0) {
Collections.sort(langs);
x.para().b().tx("Additional Language Displays");
t = x.table( "codes");
XhtmlNode tr = t.tr();
tr.td().b().tx("Code");
for (String lang : langs)
tr.td().b().addText(describeLang(lang));
for (ConceptDefinitionComponent c : cs.getConcept()) {
addLanguageRow(c, t, langs);
}
}
return hasExtensions;
}
private boolean conceptsHaveProperty(ConceptDefinitionComponent c, PropertyComponent cp) {
if (CodeSystemUtilities.hasProperty(c, cp.getCode()))
return true;
for (ConceptDefinitionComponent g : c.getConcept())
if (conceptsHaveProperty(g, cp))
return true;
return false;
}
private boolean showPropertyInTable(PropertyComponent cp) {
if (cp.hasCode()) {
if (cp.hasExtension(ToolingExtensions.EXT_RENDERED_VALUE)) {
return true;
}
if (cp.getCodeElement().hasExtension(ToolingExtensions.EXT_RENDERED_VALUE)) {
return true;
}
String uri = cp.getUri();
String code = null;
if (Utilities.noString(uri)){
return false;
}
if (uri.contains("#")) {
code = uri.substring(uri.indexOf("#")+1);
uri = uri.substring(0, uri.indexOf("#"));
}
if (Utilities.existsInList(uri, "http://hl7.org/fhir/concept-properties") || codeSystemPropList.contains(uri)) {
return true;
};
CodeSystem cs = context.fetchCodeSystem(uri);
if (cs == null) {
return false;
}
return CodeSystemUtilities.hasCode(cs, code);
}
return false;
}
private int countConcepts(List<ConceptDefinitionComponent> list) {
int count = list.size();
for (ConceptDefinitionComponent c : list)
if (c.hasConcept())
count = count + countConcepts(c.getConcept());
return count;
}
private boolean conceptsHaveComments(ConceptDefinitionComponent c) {
if (ToolingExtensions.hasCSComment(c))
return true;
for (ConceptDefinitionComponent g : c.getConcept())
if (conceptsHaveComments(g))
return true;
return false;
}
private boolean conceptsHaveDisplay(ConceptDefinitionComponent c) {
if (c.hasDisplay())
return true;
for (ConceptDefinitionComponent g : c.getConcept())
if (conceptsHaveDisplay(g))
return true;
return false;
}
private boolean conceptsHaveVersion(ConceptDefinitionComponent c) {
if (c.hasUserData("cs.version.notes"))
return true;
for (ConceptDefinitionComponent g : c.getConcept())
if (conceptsHaveVersion(g))
return true;
return false;
}
private boolean conceptsHaveDeprecated(CodeSystem cs, ConceptDefinitionComponent c) {
if (CodeSystemUtilities.isDeprecated(cs, c))
return true;
for (ConceptDefinitionComponent g : c.getConcept())
if (conceptsHaveDeprecated(cs, g))
return true;
return false;
}
private boolean addDefineRowToTable(XhtmlNode t, ConceptDefinitionComponent c, int level, boolean hasHierarchy, boolean hasDisplay, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, List<PropertyComponent> properties, CodeSystemNavigator csNav, List<String> langs) throws FHIRFormatError, DefinitionException, IOException {
boolean hasExtensions = false;
XhtmlNode tr = t.tr();
XhtmlNode td = tr.td();
if (hasHierarchy) {
td.addText(Integer.toString(level+1));
td = tr.td();
String s = Utilities.padLeft("", '\u00A0', level*2);
td.addText(s);
}
td.attribute("style", "white-space:nowrap").addText(c.getCode());
XhtmlNode a;
if (c.hasCodeElement()) {
td.an(cs.getId()+"-" + Utilities.nmtokenize(c.getCode()));
}
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.hasLanguage() && !langs.contains(cd.getLanguage())) {
langs.add(cd.getLanguage());
}
}
if (hasDisplay) {
td = tr.td();
renderDisplayName(c, cs, td);
}
td = tr.td();
if (c != null &&
c.hasDefinitionElement()) {
if (lang == null) {
if (hasMarkdownInDefinitions(cs))
addMarkdown(td, c.getDefinition());
else
td.addText(c.getDefinition());
} else if (lang.equals("*")) {
boolean sl = false;
for (ConceptDefinitionDesignationComponent cd : c.getDesignation())
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue()))
sl = true;
td.addText((sl ? cs.getLanguage("en")+": " : "")+c.getDefinition());
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) {
td.br();
td.addText(cd.getLanguage()+": "+cd.getValue());
}
}
} else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) {
td.addText(c.getDefinition());
} else {
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && cd.getLanguage().equals(lang)) {
td.addText(cd.getValue());
}
}
}
}
if (deprecated) {
td = tr.td();
Boolean b = CodeSystemUtilities.isDeprecated(cs, c);
if (b != null && b) {
smartAddText(td, context.translator().translate("xhtml-gen-cs", "Deprecated", lang));
hasExtensions = true;
if (ToolingExtensions.hasExtension(c, ToolingExtensions.EXT_REPLACED_BY)) {
Coding cc = (Coding) ToolingExtensions.getExtension(c, ToolingExtensions.EXT_REPLACED_BY).getValue();
td.tx(" (replaced by ");
String url = getCodingReference(cc, system);
if (url != null) {
td.ah(url).addText(cc.getCode());
td.tx(": "+cc.getDisplay()+")");
} else
td.addText(cc.getCode()+" '"+cc.getDisplay()+"' in "+cc.getSystem()+")");
}
}
}
if (comment) {
td = tr.td();
Extension ext = c.getExtensionByUrl(ToolingExtensions.EXT_CS_COMMENT);
if (ext != null) {
hasExtensions = true;
String bc = ext.hasValue() ? ext.getValue().primitiveValue() : null;
Map<String, String> translations = ToolingExtensions.getLanguageTranslations(ext.getValue());
if (lang == null) {
if (bc != null)
td.addText(bc);
} else if (lang.equals("*")) {
boolean sl = false;
for (String l : translations.keySet())
if (bc == null || !bc.equalsIgnoreCase(translations.get(l)))
sl = true;
if (bc != null) {
td.addText((sl ? cs.getLanguage("en")+": " : "")+bc);
}
for (String l : translations.keySet()) {
if (bc == null || !bc.equalsIgnoreCase(translations.get(l))) {
if (!td.getChildNodes().isEmpty())
td.br();
td.addText(l+": "+translations.get(l));
}
}
} else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) {
if (bc != null)
td.addText(bc);
} else {
if (bc != null)
translations.put(cs.getLanguage("en"), bc);
for (String l : translations.keySet()) {
if (l.equals(lang)) {
td.addText(translations.get(l));
}
}
}
}
}
if (version) {
td = tr.td();
if (c.hasUserData("cs.version.notes"))
td.addText(c.getUserString("cs.version.notes"));
}
if (properties != null) {
for (PropertyComponent pc : properties) {
td = tr.td();
boolean first = true;
ConceptPropertyComponent pcv = CodeSystemUtilities.getProperty(c, pc.getCode());
if (pcv != null && pcv.hasValue()) {
if (first) first = false; else td.addText(", ");
if (pcv.hasValueCoding()) {
td.addText(pcv.getValueCoding().getCode());
} else if (pcv.hasValueStringType() && Utilities.isAbsoluteUrl(pcv.getValue().primitiveValue())) {
td.ah(pcv.getValue().primitiveValue()).tx(pcv.getValue().primitiveValue());
} else {
td.addText(pcv.getValue().primitiveValue());
}
}
}
}
for (UsedConceptMap m : maps) {
td = tr.td();
List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap());
boolean first = true;
for (TargetElementComponentWrapper mapping : mappings) {
if (!first)
td.br();
first = false;
XhtmlNode span = td.span(null, mapping.comp.hasRelationship() ? mapping.comp.getRelationship().toCode() : "");
span.addText(getCharForRelationship(mapping.comp));
a = td.ah(prefix+m.getLink()+"#"+makeAnchor(mapping.group.getTarget(), mapping.comp.getCode()));
a.addText(mapping.comp.getCode());
if (!Utilities.noString(mapping.comp.getComment()))
td.i().tx("("+mapping.comp.getComment()+")");
}
}
List<ConceptDefinitionComponent> ocl = csNav.getOtherChildren(c);
for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) {
hasExtensions = addDefineRowToTable(t, cc, level+1, hasHierarchy, hasDisplay, comment, version, deprecated, maps, system, cs, properties, csNav, langs) || hasExtensions;
}
for (ConceptDefinitionComponent cc : ocl) {
tr = t.tr();
td = tr.td();
td.addText(Integer.toString(level+2));
td = tr.td();
String s = Utilities.padLeft("", '\u00A0', (level+1)*2);
td.addText(s);
td.attribute("style", "white-space:nowrap");
a = td.ah("#"+cs.getId()+"-" + Utilities.nmtokenize(cc.getCode()));
a.addText(cc.getCode());
if (hasDisplay) {
td = tr.td();
renderDisplayName(cc, cs, td);
}
int w = 1 + (deprecated ? 1 : 0) + (comment ? 1 : 0) + (version ? 1 : 0) + maps.size();
if (properties != null) {
w = w + properties.size();
}
td = tr.td().colspan(Integer.toString(w));
}
return hasExtensions;
}
private boolean hasMarkdownInDefinitions(CodeSystem cs) {
return ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown");
}
public void renderDisplayName(ConceptDefinitionComponent c, CodeSystem cs, XhtmlNode td) {
if (c.hasDisplayElement()) {
if (lang == null) {
td.addText(c.getDisplay());
} else if (lang.equals("*")) {
boolean sl = false;
for (ConceptDefinitionDesignationComponent cd : c.getDesignation())
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && !c.getDisplay().equalsIgnoreCase(cd.getValue()))
sl = true;
td.addText((sl ? cs.getLanguage("en")+": " : "")+c.getDisplay());
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && !c.getDisplay().equalsIgnoreCase(cd.getValue())) {
td.br();
td.addText(cd.getLanguage()+": "+cd.getValue());
}
}
} else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) {
td.addText(c.getDisplay());
} else {
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(lang)) {
td.addText(cd.getValue());
}
}
}
}
}
private String getCodingReference(Coding cc, String system) {
if (cc.getSystem().equals(system))
return "#"+cc.getCode();
if (cc.getSystem().equals("http://snomed.info/sct"))
return "http://snomed.info/sct/"+cc.getCode();
if (cc.getSystem().equals("http://loinc.org"))
return "http://s.details.loinc.org/LOINC/"+cc.getCode()+".html";
return null;
}
private void addLanguageRow(ConceptDefinitionComponent c, XhtmlNode t, List<String> langs) {
XhtmlNode tr = t.tr();
tr.td().addText(c.getCode());
for (String lang : langs) {
ConceptDefinitionDesignationComponent d = null;
for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) {
if (designation.hasLanguage()) {
if (lang.equals(designation.getLanguage()))
d = designation;
}
}
tr.td().addText(d == null ? "" : d.getValue());
}
}
}

View File

@ -0,0 +1,396 @@
package org.hl7.fhir.r5.terminologies;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.ContactDetail;
import org.hl7.fhir.r5.model.ContactPoint;
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r5.model.ConceptMap.OtherElementComponent;
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.NarrativeGenerator.ResourceContext;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class ConceptMapRenderer extends TerminologyRenderer {
/**
*
* @param mode - whether we are rendering for a resource directly, or for an IG
* @param context - common services (terminology server, code system cache etc)
* @param markdown - markdown processing engine of the correct sort for the version applicable
* @param prefix - the path to the base FHIR specification
* @param lang - the language to use (null for the default)
*/
public ConceptMapRenderer(TerminologyRendererMode mode, IWorkerContext context, MarkDownProcessor markdown, String prefix, String lang) {
super(mode, context, markdown, prefix, lang);
}
public boolean generate(ResourceContext rcontext, ConceptMap cm, XhtmlNode x) throws FHIRFormatError, DefinitionException, IOException {
x.h2().addText(cm.getName()+" ("+cm.getUrl()+")");
XhtmlNode p = x.para();
p.tx("Mapping from ");
if (cm.hasSource())
AddVsRef(rcontext, cm.getSource().primitiveValue(), p);
else
p.tx("(not specified)");
p.tx(" to ");
if (cm.hasTarget())
AddVsRef(rcontext, cm.getTarget().primitiveValue(), p);
else
p.tx("(not specified)");
p = x.para();
if (cm.getExperimental())
p.addText(Utilities.capitalize(cm.getStatus().toString())+" (not intended for production usage). ");
else
p.addText(Utilities.capitalize(cm.getStatus().toString())+". ");
p.tx("Published on "+(cm.hasDate() ? cm.getDateElement().toHumanDisplay() : "?ngen-10?")+" by "+cm.getPublisher());
if (!cm.getContact().isEmpty()) {
p.tx(" (");
boolean firsti = true;
for (ContactDetail ci : cm.getContact()) {
if (firsti)
firsti = false;
else
p.tx(", ");
if (ci.hasName())
p.addText(ci.getName()+": ");
boolean first = true;
for (ContactPoint c : ci.getTelecom()) {
if (first)
first = false;
else
p.tx(", ");
addTelecom(p, c);
}
}
p.tx(")");
}
p.tx(". ");
p.addText(cm.getCopyright());
if (!Utilities.noString(cm.getDescription()))
addMarkdown(x, cm.getDescription());
x.br();
CodeSystem cs = context.fetchCodeSystem("http://hl7.org/fhir/concept-map-relationship");
if (cs == null)
cs = context.fetchCodeSystem("http://hl7.org/fhir/concept-map-equivalence");
String eqpath = cs == null ? null : cs.getUserString("path");
for (ConceptMapGroupComponent grp : cm.getGroup()) {
String src = grp.getSource();
boolean comment = false;
boolean ok = true;
Map<String, HashSet<String>> sources = new HashMap<String, HashSet<String>>();
Map<String, HashSet<String>> targets = new HashMap<String, HashSet<String>>();
sources.put("code", new HashSet<String>());
targets.put("code", new HashSet<String>());
SourceElementComponent cc = grp.getElement().get(0);
String dst = grp.getTarget();
sources.get("code").add(grp.getSource());
targets.get("code").add(grp.getTarget());
for (SourceElementComponent ccl : grp.getElement()) {
ok = ok && ccl.getTarget().size() == 1 && ccl.getTarget().get(0).getDependsOn().isEmpty() && ccl.getTarget().get(0).getProduct().isEmpty();
for (TargetElementComponent ccm : ccl.getTarget()) {
comment = comment || !Utilities.noString(ccm.getComment());
for (OtherElementComponent d : ccm.getDependsOn()) {
if (!sources.containsKey(d.getProperty()))
sources.put(d.getProperty(), new HashSet<String>());
sources.get(d.getProperty()).add(d.getSystem());
}
for (OtherElementComponent d : ccm.getProduct()) {
if (!targets.containsKey(d.getProperty()))
targets.put(d.getProperty(), new HashSet<String>());
targets.get(d.getProperty()).add(d.getSystem());
}
}
}
String display;
if (ok) {
// simple
XhtmlNode tbl = x.table( "grid");
XhtmlNode tr = tbl.tr();
tr.td().b().tx("Source Code");
tr.td().b().tx("Relationship");
tr.td().b().tx("Destination Code");
if (comment)
tr.td().b().tx("Comment");
for (SourceElementComponent ccl : grp.getElement()) {
tr = tbl.tr();
XhtmlNode td = tr.td();
td.addText(ccl.getCode());
display = getDisplayForConcept(grp.getSource(), ccl.getCode());
if (display != null && !isSameCodeAndDisplay(ccl.getCode(), display))
td.tx(" ("+display+")");
TargetElementComponent ccm = ccl.getTarget().get(0);
if (!ccm.hasRelationship())
tr.td().tx(":"+"("+ConceptMapRelationship.EQUIVALENT.toCode()+")");
else {
if (ccm.getRelationshipElement().hasExtension(ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE)) {
String code = ToolingExtensions.readStringExtension(ccm.getRelationshipElement(), ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE);
tr.td().ah(eqpath+"#"+code).tx(presentEquivalenceCode(code));
} else {
tr.td().ah(eqpath+"#"+ccm.getRelationship().toCode()).tx(presentRelationshipCode(ccm.getRelationship().toCode()));
}
}
td = tr.td();
td.addText(ccm.getCode());
display = getDisplayForConcept(grp.getTarget(), ccm.getCode());
if (display != null && !isSameCodeAndDisplay(ccm.getCode(), display))
td.tx(" ("+display+")");
if (comment)
tr.td().addText(ccm.getComment());
addUnmapped(tbl, grp);
}
} else {
XhtmlNode tbl = x.table( "grid");
XhtmlNode tr = tbl.tr();
XhtmlNode td;
tr.td().colspan(Integer.toString(1+sources.size())).b().tx("Source Concept Details");
tr.td().b().tx("Relationship");
tr.td().colspan(Integer.toString(1+targets.size())).b().tx("Destination Concept Details");
if (comment) {
tr.td().b().tx("Comment");
}
tr = tbl.tr();
if (sources.get("code").size() == 1) {
String url = sources.get("code").iterator().next();
renderCSDetailsLink(tr, url, true);
} else
tr.td().b().tx("Code");
for (String s : sources.keySet()) {
if (!s.equals("code")) {
if (sources.get(s).size() == 1) {
String url = sources.get(s).iterator().next();
renderCSDetailsLink(tr, url, false);
} else
tr.td().b().addText(getDescForConcept(s));
}
}
tr.td();
if (targets.get("code").size() == 1) {
String url = targets.get("code").iterator().next();
renderCSDetailsLink(tr, url, true);
} else
tr.td().b().tx("Code");
for (String s : targets.keySet()) {
if (!s.equals("code")) {
if (targets.get(s).size() == 1) {
String url = targets.get(s).iterator().next();
renderCSDetailsLink(tr, url, false);
} else
tr.td().b().addText(getDescForConcept(s));
}
}
if (comment)
tr.td();
for (int si = 0; si < grp.getElement().size(); si++) {
SourceElementComponent ccl = grp.getElement().get(si);
boolean slast = si == grp.getElement().size()-1;
boolean first = true;
if (ccl.hasNoMap() && ccl.getNoMap()) {
tr = tbl.tr();
td = tr.td().style("border-right-width: 0px");
if (!first)
td.style("border-top-style: none");
else
td.style("border-bottom-style: none");
if (sources.get("code").size() == 1)
td.addText(ccl.getCode());
else
td.addText(grp.getSource()+" / "+ccl.getCode());
display = getDisplayForConcept(grp.getSource(), ccl.getCode());
tr.td().style("border-left-width: 0px").tx(display == null ? "" : display);
tr.td().colspan("4").style("background-color: #efefef").tx("(not mapped)");
} else {
for (int ti = 0; ti < ccl.getTarget().size(); ti++) {
TargetElementComponent ccm = ccl.getTarget().get(ti);
boolean last = ti == ccl.getTarget().size()-1;
tr = tbl.tr();
td = tr.td().style("border-right-width: 0px");
if (!first && !last)
td.style("border-top-style: none; border-bottom-style: none");
else if (!first)
td.style("border-top-style: none");
else if (!last)
td.style("border-bottom-style: none");
if (first) {
if (sources.get("code").size() == 1)
td.addText(ccl.getCode());
else
td.addText(grp.getSource()+" / "+ccl.getCode());
display = getDisplayForConcept(grp.getSource(), ccl.getCode());
tr.td().style("border-left-width: 0px").tx(display == null ? "" : display);
}
for (String s : sources.keySet()) {
if (!s.equals("code")) {
td = tr.td();
if (first) {
td.addText(getValue(ccm.getDependsOn(), s, sources.get(s).size() != 1));
display = getDisplay(ccm.getDependsOn(), s);
if (display != null)
td.tx(" ("+display+")");
}
}
}
first = false;
if (!ccm.hasRelationship())
tr.td().tx(":"+"("+ConceptMapRelationship.EQUIVALENT.toCode()+")");
else {
if (ccm.getRelationshipElement().hasExtension(ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE)) {
String code = ToolingExtensions.readStringExtension(ccm.getRelationshipElement(), ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE);
tr.td().ah(eqpath+"#"+code).tx(presentEquivalenceCode(code));
} else {
tr.td().ah(eqpath+"#"+ccm.getRelationship().toCode()).tx(presentRelationshipCode(ccm.getRelationship().toCode()));
}
}
td = tr.td().style("border-right-width: 0px");
if (targets.get("code").size() == 1)
td.addText(ccm.getCode());
else
td.addText(grp.getTarget()+" / "+ccm.getCode());
display = getDisplayForConcept(grp.getTarget(), ccm.getCode());
tr.td().style("border-left-width: 0px").tx(display == null ? "" : display);
for (String s : targets.keySet()) {
if (!s.equals("code")) {
td = tr.td();
td.addText(getValue(ccm.getProduct(), s, targets.get(s).size() != 1));
display = getDisplay(ccm.getProduct(), s);
if (display != null)
td.tx(" ("+display+")");
}
}
if (comment)
tr.td().addText(ccm.getComment());
}
}
addUnmapped(tbl, grp);
}
}
}
return true;
}
private boolean isSameCodeAndDisplay(String code, String display) {
String c = code.replace(" ", "").replace("-", "").toLowerCase();
String d = display.replace(" ", "").replace("-", "").toLowerCase();
return c.equals(d);
}
private String presentRelationshipCode(String code) {
if ("related-to".equals(code)) {
return "is related to";
} else if ("equivalent".equals(code)) {
return "is equivalent to";
} else if ("broader".equals(code)) {
return "maps to wider concept";
} else if ("narrower".equals(code)) {
return "maps to narrower concept";
} else if ("not-related-to".equals(code)) {
return "is not related to";
} else {
return code;
}
}
private String presentEquivalenceCode(String code) {
if ("relatedto".equals(code)) {
return "is related to";
} else if ("equivalent".equals(code)) {
return "is equivalent to";
} else if ("equal".equals(code)) {
return "is equal to";
} else if ("wider".equals(code)) {
return "maps to wider concept";
} else if ("subsumes".equals(code)) {
return "is subsumed by";
} else if ("narrower".equals(code)) {
return "maps to narrower concept";
} else if ("specializes".equals(code)) {
return "has specialization";
} else if ("inexact".equals(code)) {
return "maps loosely to";
} else if ("unmatched".equals(code)) {
return "has no match";
} else if ("disjoint".equals(code)) {
return "is not related to";
} else {
return code;
}
}
public void renderCSDetailsLink(XhtmlNode tr, String url, boolean span2) {
CodeSystem cs;
XhtmlNode td;
cs = context.fetchCodeSystem(url);
td = tr.td();
if (span2) {
td.colspan("2");
}
td.b().tx("Code");
td.tx(" from ");
if (cs == null)
td.tx(url);
else
td.ah(cs.getUserString("path")).attribute("title", url).tx(cs.present());
}
private void addUnmapped(XhtmlNode tbl, ConceptMapGroupComponent grp) {
if (grp.hasUnmapped()) {
// throw new Error("not done yet");
}
}
private String getDescForConcept(String s) {
if (s.startsWith("http://hl7.org/fhir/v2/element/"))
return "v2 "+s.substring("http://hl7.org/fhir/v2/element/".length());
return s;
}
private String getValue(List<OtherElementComponent> list, String s, boolean withSystem) {
for (OtherElementComponent c : list) {
if (s.equals(c.getProperty()))
if (withSystem)
return c.getSystem()+" / "+c.getValue();
else
return c.getValue();
}
return null;
}
private String getDisplay(List<OtherElementComponent> list, String s) {
for (OtherElementComponent c : list) {
if (s.equals(c.getProperty()))
return getDisplayForConcept(c.getSystem(), c.getValue());
}
return null;
}
}

View File

@ -0,0 +1,10 @@
package org.hl7.fhir.r5.terminologies;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.Coding;
public abstract class SpecialCodeSystem {
public abstract ConceptDefinitionComponent findConcept(Coding code);
}

View File

@ -0,0 +1,495 @@
package org.hl7.fhir.r5.terminologies;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.ContactPoint;
import org.hl7.fhir.r5.model.Questionnaire;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem;
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.NarrativeGenerator.ResourceContext;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
public class TerminologyRenderer {
public enum TerminologyRendererMode {
RESOURCE, IG
}
protected class TargetElementComponentWrapper {
protected ConceptMapGroupComponent group;
protected TargetElementComponent comp;
protected TargetElementComponentWrapper(ConceptMapGroupComponent group, TargetElementComponent comp) {
super();
this.group = group;
this.comp = comp;
}
}
public class UsedConceptMap {
private ConceptMapRenderInstructions details;
private String link;
private ConceptMap map;
public UsedConceptMap(ConceptMapRenderInstructions details, String link, ConceptMap map) {
super();
this.details = details;
this.link = link;
this.map = map;
}
public ConceptMapRenderInstructions getDetails() {
return details;
}
public ConceptMap getMap() {
return map;
}
public String getLink() {
return link;
}
}
public class ConceptMapRenderInstructions {
private String name;
private String url;
private boolean doDescription;
public ConceptMapRenderInstructions(String name, String url, boolean doDescription) {
super();
this.name = name;
this.url = url;
this.doDescription = doDescription;
}
public String getName() {
return name;
}
public String getUrl() {
return url;
}
public boolean isDoDescription() {
return doDescription;
}
}
protected TerminologyRendererMode mode;
protected IWorkerContext context;
protected MarkDownProcessor markdown;
protected String lang;
protected String prefix;
protected int headerLevelContext;
protected ValidationOptions terminologyServiceOptions = new ValidationOptions();
protected boolean noSlowLookup;
/**
*
* @param mode - whether we are rendering for a resource directly, or for an IG
* @param context - common services (terminology server, code system cache etc)
* @param markdown - markdown processing engine of the correct sort for the version applicable
* @param prefix - the path to the base FHIR specification
* @param lang - the language to use (null for the default)
*/
public TerminologyRenderer(TerminologyRendererMode mode, IWorkerContext context, MarkDownProcessor markdown, String prefix, String lang) {
super();
this.mode = mode;
this.context = context;
this.markdown = markdown;
this.lang = lang;
this.prefix = prefix;
}
public TerminologyRendererMode getMode() {
return mode;
}
public int getHeaderLevelContext() {
return headerLevelContext;
}
public void setHeaderLevelContext(int headerLevelContext) {
this.headerLevelContext = headerLevelContext;
}
public ValidationOptions getTerminologyServiceOptions() {
return terminologyServiceOptions;
}
public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
this.terminologyServiceOptions = terminologyServiceOptions;
}
public boolean isNoSlowLookup() {
return noSlowLookup;
}
public void setNoSlowLookup(boolean noSlowLookup) {
this.noSlowLookup = noSlowLookup;
}
protected void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException {
if (text != null) {
// 1. custom FHIR extensions
while (text.contains("[[[")) {
String left = text.substring(0, text.indexOf("[[["));
String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]"));
String right = text.substring(text.indexOf("]]]")+3);
String url = link;
String[] parts = link.split("\\#");
StructureDefinition p = context.fetchResource(StructureDefinition.class, parts[0]);
if (p == null)
p = context.fetchTypeDefinition(parts[0]);
if (p == null)
p = context.fetchResource(StructureDefinition.class, link);
if (p != null) {
url = p.getUserString("path");
if (url == null)
url = p.getUserString("filename");
} else
throw new DefinitionException("Unable to resolve markdown link "+link);
text = left+"["+link+"]("+url+")"+right;
}
// 2. markdown
String s = markdown.process(Utilities.escapeXml(text), "narrative generator");
XhtmlParser p = new XhtmlParser();
XhtmlNode m;
try {
m = p.parse("<div>"+s+"</div>", "div");
} catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
throw new FHIRFormatError(e.getMessage(), e);
}
x.getChildNodes().addAll(m.getChildNodes());
}
}
protected void generateCopyright(XhtmlNode x, CanonicalResource cs) {
XhtmlNode p = x.para();
p.b().tx(context.translator().translate("xhtml-gen-cs", "Copyright Statement:", lang));
smartAddText(p, " " + cs.getCopyright());
}
protected void smartAddText(XhtmlNode p, String text) {
if (text == null)
return;
String[] lines = text.split("\\r\\n");
for (int i = 0; i < lines.length; i++) {
if (i > 0)
p.br();
p.addText(lines[i]);
}
}
protected void addMapHeaders(XhtmlNode tr, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException {
for (UsedConceptMap m : maps) {
XhtmlNode td = tr.td();
XhtmlNode b = td.b();
XhtmlNode a = b.ah(prefix+m.getLink());
a.addText(m.getDetails().getName());
if (m.getDetails().isDoDescription() && m.getMap().hasDescription())
addMarkdown(td, m.getMap().getDescription());
}
}
protected String getHeader() {
int i = 3;
while (i <= headerLevelContext)
i++;
if (i > 6)
i = 6;
return "h"+Integer.toString(i);
}
public static String describeSystem(String system) {
if (system == null)
return "[not stated]";
if (system.equals("http://loinc.org"))
return "LOINC";
if (system.startsWith("http://snomed.info"))
return "SNOMED CT";
if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm"))
return "RxNorm";
if (system.equals("http://hl7.org/fhir/sid/icd-9"))
return "ICD-9";
if (system.equals("http://dicom.nema.org/resources/ontology/DCM"))
return "DICOM";
if (system.equals("http://unitsofmeasure.org"))
return "UCUM";
return system;
}
protected String makeAnchor(String codeSystem, String code) {
String s = codeSystem+'-'+code;
StringBuilder b = new StringBuilder();
for (char c : s.toCharArray()) {
if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '.')
b.append(c);
else
b.append('-');
}
return b.toString();
}
protected List<TargetElementComponentWrapper> findMappingsForCode(String code, ConceptMap map) {
List<TargetElementComponentWrapper> mappings = new ArrayList<TargetElementComponentWrapper>();
for (ConceptMapGroupComponent g : map.getGroup()) {
for (SourceElementComponent c : g.getElement()) {
if (c.getCode().equals(code))
for (TargetElementComponent cc : c.getTarget())
mappings.add(new TargetElementComponentWrapper(g, cc));
}
}
return mappings;
}
protected String getCharForRelationship(TargetElementComponent mapping) {
if (!mapping.hasRelationship())
return "";
switch (mapping.getRelationship()) {
case EQUIVALENT : return "~";
case BROADER : return "<";
case NARROWER : return ">";
case NOTRELATEDTO : return "!=";
default: return "?";
}
}
protected <T extends Resource> void addCsRef(ConceptSetComponent inc, XhtmlNode li, T cs) {
String ref = null;
boolean addHtml = true;
if (cs != null) {
ref = (String) cs.getUserData("external.url");
if (Utilities.noString(ref))
ref = (String) cs.getUserData("filename");
else
addHtml = false;
if (Utilities.noString(ref))
ref = (String) cs.getUserData("path");
}
String spec = getSpecialReference(inc.getSystem());
if (spec != null) {
XhtmlNode a = li.ah(spec);
a.code(inc.getSystem());
} else if (cs != null && ref != null) {
if (!Utilities.noString(prefix) && ref.startsWith("http://hl7.org/fhir/"))
ref = ref.substring(20)+"/index.html";
else if (addHtml && !ref.contains(".html"))
ref = ref + ".html";
XhtmlNode a = li.ah(prefix+ref.replace("\\", "/"));
a.code(inc.getSystem());
} else {
li.code(inc.getSystem());
}
}
private String getSpecialReference(String system) {
if ("http://snomed.info/sct".equals(system))
return "http://www.snomed.org/";
if (Utilities.existsInList(system, "http://loinc.org", "http://unitsofmeasure.org", "http://www.nlm.nih.gov/research/umls/rxnorm", "http://ncimeta.nci.nih.gov", "http://fdasis.nlm.nih.gov",
"http://www.radlex.org", "http://www.whocc.no/atc", "http://dicom.nema.org/resources/ontology/DCM", "http://www.genenames.org", "http://www.ensembl.org", "http://www.ncbi.nlm.nih.gov/nuccore",
"http://www.ncbi.nlm.nih.gov/clinvar", "http://sequenceontology.org", "http://www.hgvs.org/mutnomen", "http://www.ncbi.nlm.nih.gov/projects/SNP", "http://cancer.sanger.ac.uk/cancergenome/projects/cosmic",
"http://www.lrg-sequence.org", "http://www.omim.org", "http://www.ncbi.nlm.nih.gov/pubmed", "http://www.pharmgkb.org", "http://clinicaltrials.gov", "http://www.ebi.ac.uk/ipd/imgt/hla/"))
return system;
return null;
}
protected XhtmlNode addTableHeaderRowStandard(XhtmlNode t, boolean hasHierarchy, boolean hasDisplay, boolean definitions, boolean comments, boolean version, boolean deprecated, List<PropertyComponent> properties) {
XhtmlNode tr = t.tr();
if (hasHierarchy)
tr.td().b().tx("Lvl");
tr.td().attribute("style", "white-space:nowrap").b().tx(context.translator().translate("xhtml-gen-cs", "Code", lang));
if (hasDisplay)
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Display", lang));
if (definitions)
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Definition", lang));
if (deprecated)
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Deprecated", lang));
if (comments)
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Comments", lang));
if (version)
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Version", lang));
if (properties != null) {
for (PropertyComponent pc : properties) {
String display = ToolingExtensions.getPresentation(pc, pc.getCodeElement());
if (display == null || display.equals(pc.getCode()) && pc.hasUri()) {
display = getDisplayForProperty(pc.getUri());
if (display == null) {
display = pc.getCode();
}
}
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", display, lang));
}
}
return tr;
}
protected String getDisplayForProperty(String uri) {
if (Utilities.noString(uri)){
return null;
}
String code = null;
if (uri.contains("#")) {
code = uri.substring(uri.indexOf("#")+1);
uri = uri.substring(0, uri.indexOf("#"));
}
CodeSystem cs = context.fetchCodeSystem(uri);
if (cs == null) {
return null;
}
ConceptDefinitionComponent cc = CodeSystemUtilities.getCode(cs, code);
return cc == null ? null : cc.getDisplay();
}
protected void AddVsRef(ResourceContext rcontext, String value, XhtmlNode li) {
Resource res = null;
if (rcontext != null) {
BundleEntryComponent be = rcontext.resolve(value);
if (be != null) {
res = be.getResource();
}
}
if (res != null && !(res instanceof CanonicalResource)) {
li.addText(value);
return;
}
CanonicalResource vs = (CanonicalResource) res;
if (vs == null)
vs = context.fetchResource(ValueSet.class, value);
if (vs == null)
vs = context.fetchResource(StructureDefinition.class, value);
// if (vs == null)
// vs = context.fetchResource(DataElement.class, value);
if (vs == null)
vs = context.fetchResource(Questionnaire.class, value);
if (vs != null) {
String ref = (String) vs.getUserData("path");
ref = adjustForPath(ref);
XhtmlNode a = li.ah(ref == null ? "?ngen-11?" : ref.replace("\\", "/"));
a.addText(value);
} else {
CodeSystem cs = context.fetchCodeSystem(value);
if (cs != null) {
String ref = (String) cs.getUserData("path");
ref = adjustForPath(ref);
XhtmlNode a = li.ah(ref == null ? "?ngen-12?" : ref.replace("\\", "/"));
a.addText(value);
} else if (value.equals("http://snomed.info/sct") || value.equals("http://snomed.info/id")) {
XhtmlNode a = li.ah(value);
a.tx("SNOMED-CT");
}
else {
if (value.startsWith("http://hl7.org") && !Utilities.existsInList(value, "http://hl7.org/fhir/sid/icd-10-us"))
System.out.println("Unable to resolve value set "+value);
li.addText(value);
}
}
}
private String adjustForPath(String ref) {
if (prefix == null)
return ref;
else
return prefix+ref;
}
protected void addTelecom(XhtmlNode p, ContactPoint c) {
if (c.getSystem() == ContactPointSystem.PHONE) {
p.tx("Phone: "+c.getValue());
} else if (c.getSystem() == ContactPointSystem.FAX) {
p.tx("Fax: "+c.getValue());
} else if (c.getSystem() == ContactPointSystem.EMAIL) {
p.ah( "mailto:"+c.getValue()).addText(c.getValue());
} else if (c.getSystem() == ContactPointSystem.URL) {
if (c.getValue().length() > 30)
p.ah(c.getValue()).addText(c.getValue().substring(0, 30)+"...");
else
p.ah(c.getValue()).addText(c.getValue());
}
}
protected String getDisplayForConcept(String system, String value) {
if (value == null || system == null)
return null;
ValidationResult cl = context.validateCode(terminologyServiceOptions, system, value, null);
return cl == null ? null : cl.getDisplay();
}
protected String describeLang(String lang) {
ValueSet v = context.fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages");
if (v != null) {
ConceptReferenceComponent l = null;
for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) {
if (cc.getCode().equals(lang))
l = cc;
}
if (l == null) {
if (lang.contains("-"))
lang = lang.substring(0, lang.indexOf("-"));
for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) {
if (cc.getCode().equals(lang) || cc.getCode().startsWith(lang+"-"))
l = cc;
}
}
if (l != null) {
if (lang.contains("-"))
lang = lang.substring(0, lang.indexOf("-"));
String en = l.getDisplay();
String nativelang = null;
for (ConceptReferenceDesignationComponent cd : l.getDesignation()) {
if (cd.getLanguage().equals(lang))
nativelang = cd.getValue();
}
if (nativelang == null)
return en+" ("+lang+")";
else
return nativelang+" ("+en+", "+lang+")";
}
}
return lang;
}
}

View File

@ -0,0 +1,18 @@
package org.hl7.fhir.r5.terminologies;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.utilities.Utilities;
public class URICodeSystem extends SpecialCodeSystem {
@Override
public ConceptDefinitionComponent findConcept(Coding code) {
if (Utilities.isAbsoluteUrl(code.getCode())) {
return new ConceptDefinitionComponent(code.getCode());
} else {
return null;
}
}
}

View File

@ -112,10 +112,17 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
if (system == null && !code.hasDisplay()) { // dealing with just a plain code (enum)
system = systemForCodeInValueSet(code.getCode());
}
if (!code.hasSystem())
if (!code.hasSystem()) {
if (options.isGuessSystem() && system == null && Utilities.isAbsoluteUrl(code.getCode())) {
system = "urn:ietf:rfc:3986"; // this arises when using URIs bound to value sets
}
code.setSystem(system);
}
inExpansion = checkExpansion(code);
CodeSystem cs = context.fetchCodeSystem(system);
if (cs == null) {
cs = findSpecialCodeSystem(system);
}
if (cs == null) {
warningMessage = "Unable to resolve system "+system+" - system is not specified or implicit";
if (!inExpansion)
@ -123,7 +130,7 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
}
if (cs!=null && cs.getContent() != CodeSystemContentMode.COMPLETE) {
warningMessage = "Unable to resolve system "+system+" - system is not complete";
if (!inExpansion)
if (!inExpansion && cs.getContent() != CodeSystemContentMode.FRAGMENT) // we're going to give it a go if it's a fragment
throw new FHIRException(warningMessage);
}
@ -147,6 +154,17 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
return res;
}
private CodeSystem findSpecialCodeSystem(String system) {
if ("urn:ietf:rfc:3986".equals(system)) {
CodeSystem cs = new CodeSystem();
cs.setUrl(system);
cs.setUserData("tx.cs.special", new URICodeSystem());
cs.setContent(CodeSystemContentMode.COMPLETE);
return cs;
}
return null;
}
boolean checkExpansion(Coding code) {
if (valueset==null || !valueset.hasExpansion())
return false;
@ -164,9 +182,14 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
}
private ValidationResult validateCode(Coding code, CodeSystem cs) {
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code.getCode());
if (cc == null)
return new ValidationResult(IssueSeverity.ERROR, context.formatMessage(I18nConstants.UNKNOWN_CODE__IN_, gen(code), cs.getUrl()));
ConceptDefinitionComponent cc = cs.hasUserData("tx.cs.special") ? ((SpecialCodeSystem) cs.getUserData("tx.cs.special")).findConcept(code) : findCodeInConcept(cs.getConcept(), code.getCode());
if (cc == null) {
if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
return new ValidationResult(IssueSeverity.ERROR, context.formatMessage(I18nConstants.UNKNOWN_CODE__IN_FRAGMENT, gen(code), cs.getUrl()));
} else {
return new ValidationResult(IssueSeverity.ERROR, context.formatMessage(I18nConstants.UNKNOWN_CODE__IN_, gen(code), cs.getUrl()));
}
}
if (code.getDisplay() == null)
return new ValidationResult(cc);
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();

View File

@ -101,6 +101,7 @@ public class ValueSetExpanderSimple implements ValueSetExpander {
private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>();
private IWorkerContext context;
private boolean canBeHeirarchy = true;
private boolean includeAbstract = true;
private Set<String> excludeKeys = new HashSet<String>();
private Set<String> excludeSystems = new HashSet<String>();
private ValueSet focus;
@ -213,7 +214,7 @@ public class ValueSetExpanderSimple implements ValueSetExpander {
ValueSetExpansionContainsComponent np = null;
boolean abs = CodeSystemUtilities.isNotSelectable(cs, def);
boolean inc = CodeSystemUtilities.isInactive(cs, def);
if (canBeHeirarchy || !abs)
if (includeAbstract || !abs)
np = addCode(system, def.getCode(), def.getDisplay(), parent, def.getDesignation(), expParams, abs, inc, filters);
for (ConceptDefinitionComponent c : def.getConcept()) {
addCodeAndDescendents(cs, system, c, np, expParams, filters, exclusion);
@ -340,7 +341,7 @@ public class ValueSetExpanderSimple implements ValueSetExpander {
}
} else {
for (ValueSetExpansionContainsComponent c : codes) {
if (map.containsKey(key(c)) && !c.getAbstract()) { // we may have added abstract codes earlier while we still thought it might be heirarchical, but later we gave up, so now ignore them
if (map.containsKey(key(c)) && (includeAbstract || !c.getAbstract())) { // we may have added abstract codes earlier while we still thought it might be heirarchical, but later we gave up, so now ignore them
focus.getExpansion().getContains().add(c);
c.getContains().clear(); // make sure any heirarchy is wiped
}
@ -379,6 +380,7 @@ public class ValueSetExpanderSimple implements ValueSetExpander {
for (ConceptSetComponent inc : compose.getExclude())
excludeCodes(inc, exp.getParameter(), ctxt);
canBeHeirarchy = !expParams.getParameterBool("excludeNested") && excludeKeys.isEmpty() && excludeSystems.isEmpty();
includeAbstract = !expParams.getParameterBool("excludeNotForUI");
boolean first = true;
for (ConceptSetComponent inc : compose.getInclude()) {
if (first == true)
@ -560,6 +562,12 @@ public class ValueSetExpanderSimple implements ValueSetExpander {
throw new TerminologyServiceException("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
for (ConceptDefinitionComponent c : def.getConcept())
addCodeAndDescendents(cs, inc.getSystem(), c, null, expParams, imports, null);
if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
for (ConceptDefinitionComponent c : children)
addCodeAndDescendents(cs, inc.getSystem(), c, null, expParams, imports, null);
}
} else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) {
// gg; note: wtf is this: if the filter is display=v, look up the code 'v', and see if it's diplsay is 'v'?
canBeHeirarchy = false;

View File

@ -0,0 +1,996 @@
package org.hl7.fhir.r5.terminologies;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.ExtensionHelper;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
import org.hl7.fhir.r5.terminologies.TerminologyRenderer.ConceptMapRenderInstructions;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.NarrativeGenerator.ResourceContext;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
public class ValueSetRenderer extends TerminologyRenderer {
private static final String ABSTRACT_CODE_HINT = "This code is not selectable ('Abstract')";
private String tooCostlyNoteEmpty;
private String tooCostlyNoteNotEmpty;
private String tooCostlyNoteEmptyDependent;
private String tooCostlyNoteNotEmptyDependent;
private List<ConceptMapRenderInstructions> renderingMaps = new ArrayList<ConceptMapRenderInstructions>();
/**
*
* @param mode - whether we are rendering for a resource directly, or for an IG
* @param context - common services (terminology server, code system cache etc)
* @param markdown - markdown processing engine of the correct sort for the version applicable
* @param prefix - the path to the base FHIR specification
* @param lang - the language to use (null for the default)
*/
public ValueSetRenderer(TerminologyRendererMode mode, IWorkerContext context, MarkDownProcessor markdown, String prefix, String lang) {
super(mode, context, markdown, prefix, lang);
renderingMaps.add(new ConceptMapRenderInstructions("Canonical Status", "http://hl7.org/fhir/ValueSet/resource-status", false));
}
public String getTooCostlyNoteEmpty() {
return tooCostlyNoteEmpty;
}
public void setTooCostlyNoteEmpty(String tooCostlyNoteEmpty) {
this.tooCostlyNoteEmpty = tooCostlyNoteEmpty;
}
public String getTooCostlyNoteNotEmpty() {
return tooCostlyNoteNotEmpty;
}
public void setTooCostlyNoteNotEmpty(String tooCostlyNoteNotEmpty) {
this.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty;
}
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;
}
public boolean render(ResourceContext rcontext, XhtmlNode x, ValueSet vs, ValueSet src, boolean header) throws FHIRFormatError, DefinitionException, IOException {
List<UsedConceptMap> maps = findReleventMaps(vs);
boolean hasExtensions;
if (vs.hasExpansion()) {
// for now, we just accept an expansion if there is one
hasExtensions = generateExpansion(x, vs, src, header, maps);
} else {
hasExtensions = generateComposition(rcontext, x, vs, header, maps);
}
return hasExtensions;
}
private List<UsedConceptMap> findReleventMaps(ValueSet vs) throws FHIRException {
List<UsedConceptMap> res = new ArrayList<UsedConceptMap>();
for (CanonicalResource md : context.allConformanceResources()) {
if (md instanceof ConceptMap) {
ConceptMap cm = (ConceptMap) md;
if (isSource(vs, cm.getSource())) {
ConceptMapRenderInstructions re = findByTarget(cm.getTarget());
if (re != null) {
ValueSet vst = cm.hasTarget() ? context.fetchResource(ValueSet.class, cm.hasTargetCanonicalType() ? cm.getTargetCanonicalType().getValue() : cm.getTargetUriType().asStringValue()) : null;
res.add(new UsedConceptMap(re, vst == null ? cm.getUserString("path") : vst.getUserString("path"), cm));
}
}
}
}
return res;
// Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>();
// for (ConceptMap a : context.findMapsForSource(vs.getUrl())) {
// String url = "";
// ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference());
// if (vsr != null)
// url = (String) vsr.getUserData("filename");
// mymaps.put(a, url);
// }
// Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>();
// for (ConceptMap a : context.findMapsForSource(cs.getValueSet())) {
// String url = "";
// ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference());
// if (vsr != null)
// url = (String) vsr.getUserData("filename");
// mymaps.put(a, url);
// }
// also, look in the contained resources for a concept map
// for (Resource r : cs.getContained()) {
// if (r instanceof ConceptMap) {
// ConceptMap cm = (ConceptMap) r;
// if (((Reference) cm.getSource()).getReference().equals(cs.getValueSet())) {
// String url = "";
// ValueSet vsr = context.fetchResource(ValueSet.class, ((Reference) cm.getTarget()).getReference());
// if (vsr != null)
// url = (String) vsr.getUserData("filename");
// mymaps.put(cm, url);
// }
// }
// }
}
private boolean isSource(ValueSet vs, DataType source) {
return vs.hasUrl() && source != null && vs.getUrl().equals(source.primitiveValue());
}
private boolean generateExpansion(XhtmlNode x, ValueSet vs, ValueSet src, boolean header, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException {
boolean hasExtensions = false;
List<String> langs = new ArrayList<String>();
if (header) {
XhtmlNode h = x.addTag(getHeader());
h.tx("Value Set Contents");
if (IsNotFixedExpansion(vs))
addMarkdown(x, vs.getDescription());
if (vs.hasCopyright())
generateCopyright(x, vs);
}
if (ToolingExtensions.hasExtension(vs.getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY)) {
List<Extension> exl = vs.getExpansion().getExtensionsByUrl(ToolingExtensions.EXT_EXP_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");
else
x.para().tx("This value set contains "+count.toString()+" concepts");
}
if (ToolingExtensions.hasExtension(vs.getExpansion(), ToolingExtensions.EXT_EXP_FRAGMENT)) {
XhtmlNode div = x.div().style("border: maroon 1px solid; background-color: #FFCCCC; padding: 8px");
List<Extension> exl = vs.getExpansion().getExtensionsByUrl(ToolingExtensions.EXT_EXP_FRAGMENT);
if (exl.size() > 1) {
div.para().addText("Warning: this expansion is generated from fragments of the following code systems, and may be missing codes, or include codes that are not valid:");
XhtmlNode ul = div.ul();
for (Extension ex : exl) {
addCSRef(ul.li(), ex.getValue().primitiveValue());
}
} else {
XhtmlNode p = div.para();
p.addText("Warning: this expansion is generated from a fragment of the code system ");
addCSRef(p, exl.get(0).getValue().primitiveValue());
p.addText(" and may be missing codes, or include codes that are not valid");
}
}
generateVersionNotice(x, vs.getExpansion());
CodeSystem allCS = null;
boolean doLevel = false;
for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
if (cc.hasContains()) {
doLevel = true;
break;
}
}
boolean doSystem = true; // checkDoSystem(vs, src);
boolean doDefinition = checkDoDefinition(vs.getExpansion().getContains());
if (doSystem && allFromOneSystem(vs)) {
doSystem = false;
XhtmlNode p = x.para();
p.tx("All codes from system ");
allCS = context.fetchCodeSystem(vs.getExpansion().getContains().get(0).getSystem());
String ref = null;
if (allCS != null)
ref = getCsRef(allCS);
if (ref == null)
p.code(vs.getExpansion().getContains().get(0).getSystem());
else
p.ah(prefix+ref).code(vs.getExpansion().getContains().get(0).getSystem());
}
XhtmlNode t = x.table( "codes");
XhtmlNode tr = t.tr();
if (doLevel)
tr.td().b().tx("Lvl");
tr.td().attribute("style", "white-space:nowrap").b().tx("Code");
if (doSystem)
tr.td().b().tx("System");
tr.td().b().tx("Display");
if (doDefinition)
tr.td().b().tx("Definition");
addMapHeaders(tr, maps);
for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
addExpansionRowToTable(t, c, 0, doLevel, doSystem, doDefinition, maps, allCS, langs);
}
// now, build observed languages
if (langs.size() > 0) {
Collections.sort(langs);
x.para().b().tx("Additional Language Displays");
t = x.table( "codes");
tr = t.tr();
tr.td().b().tx("Code");
for (String lang : langs)
tr.td().b().addText(describeLang(lang));
for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
addLanguageRow(c, t, langs);
}
}
return hasExtensions;
}
private boolean checkDoSystem(ValueSet vs, ValueSet src) {
if (src != null)
vs = src;
return vs.hasCompose();
}
private boolean IsNotFixedExpansion(ValueSet vs) {
if (vs.hasCompose())
return false;
// it's not fixed if it has any includes that are not version fixed
for (ConceptSetComponent cc : vs.getCompose().getInclude()) {
if (cc.hasValueSet())
return true;
if (!cc.hasVersion())
return true;
}
return false;
}
private ConceptMapRenderInstructions findByTarget(DataType source) {
if (source == null) {
return null;
}
String src = source.primitiveValue();
if (src != null)
for (ConceptMapRenderInstructions t : renderingMaps) {
if (src.equals(t.getUrl()))
return t;
}
return null;
}
private Integer countMembership(ValueSet vs) {
int count = 0;
if (vs.hasExpansion())
count = count + conceptCount(vs.getExpansion().getContains());
else {
if (vs.hasCompose()) {
if (vs.getCompose().hasExclude()) {
try {
ValueSetExpansionOutcome vse = context.expandVS(vs, true, false);
count = 0;
count += conceptCount(vse.getValueset().getExpansion().getContains());
return count;
} catch (Exception e) {
return null;
}
}
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
if (inc.hasFilter())
return null;
if (!inc.hasConcept())
return null;
count = count + inc.getConcept().size();
}
}
}
return count;
}
private int conceptCount(List<ValueSetExpansionContainsComponent> list) {
int count = 0;
for (ValueSetExpansionContainsComponent c : list) {
if (!c.getAbstract())
count++;
count = count + conceptCount(c.getContains());
}
return count;
}
private void addCSRef(XhtmlNode x, String url) {
CodeSystem cs = context.fetchCodeSystem(url);
if (cs == null) {
x.code(url);
} else if (cs.hasUserData("path")) {
x.ah(cs.getUserString("path")).tx(cs.present());
} else {
x.code(url);
x.tx(" ("+cs.present()+")");
}
}
@SuppressWarnings("rawtypes")
private void generateVersionNotice(XhtmlNode x, ValueSetExpansionComponent expansion) {
Multimap<String, String> versions = HashMultimap.create();
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.size() > 0) {
XhtmlNode div = null;
XhtmlNode ul = null;
boolean first = true;
for (String s : versions.keySet()) {
if (versions.size() == 1 && versions.get(s).size() == 1) {
for (String v : versions.get(s)) { // though there'll only be one
XhtmlNode p = x.para().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px");
p.tx("Expansion based on ");
expRef(p, s, v);
}
} else {
for (String v : versions.get(s)) {
if (first) {
div = x.div().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px");
div.para().tx("Expansion based on: ");
ul = div.ul();
first = false;
}
expRef(ul.li(), s, v);
}
}
}
}
}
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) {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
Date date;
try {
date = format.parse(ds);
} catch (ParseException e) {
return ds;
}
return new SimpleDateFormat("dd-MMM yyyy").format(date);
}
private String describeModule(String module) {
if ("900000000000207008".equals(module))
return "International";
if ("731000124108".equals(module))
return "United States";
if ("32506021000036107".equals(module))
return "Australian";
if ("449081005".equals(module))
return "Spanish";
if ("554471000005108".equals(module))
return "Danish";
if ("11000146104".equals(module))
return "Dutch";
if ("45991000052106".equals(module))
return "Swedish";
if ("999000041000000102".equals(module))
return "United Kingdon";
return module;
}
private boolean hasVersionParameter(ValueSetExpansionComponent expansion) {
for (ValueSetExpansionParameterComponent p : expansion.getParameter()) {
if (p.getName().equals("version"))
return true;
}
return false;
}
private void addLanguageRow(ValueSetExpansionContainsComponent c, XhtmlNode t, List<String> langs) {
XhtmlNode tr = t.tr();
tr.td().addText(c.getCode());
for (String lang : langs) {
String d = null;
for (Extension ext : c.getExtension()) {
if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) {
String l = ToolingExtensions.readStringExtension(ext, "lang");
if (lang.equals(l))
d = ToolingExtensions.readStringExtension(ext, "content");
}
}
tr.td().addText(d == null ? "" : d);
}
for (ValueSetExpansionContainsComponent cc : c.getContains()) {
addLanguageRow(cc, t, langs);
}
}
private boolean checkDoDefinition(List<ValueSetExpansionContainsComponent> contains) {
for (ValueSetExpansionContainsComponent c : contains) {
CodeSystem cs = context.fetchCodeSystem(c.getSystem());
if (cs != null)
return true;
if (checkDoDefinition(c.getContains()))
return true;
}
return false;
}
private boolean allFromOneSystem(ValueSet vs) {
if (vs.getExpansion().getContains().isEmpty())
return false;
String system = vs.getExpansion().getContains().get(0).getSystem();
for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
if (!checkSystemMatches(system, cc))
return false;
}
return true;
}
private String getCsRef(String system) {
CodeSystem cs = context.fetchCodeSystem(system);
return getCsRef(cs);
}
private <T extends Resource> String getCsRef(T cs) {
String ref = (String) cs.getUserData("filename");
if (ref == null)
ref = (String) cs.getUserData("path");
if (ref == null)
return "?ngen-14?.html";
if (!ref.contains(".html"))
ref = ref + ".html";
return ref.replace("\\", "/");
}
private void addExpansionRowToTable(XhtmlNode t, ValueSetExpansionContainsComponent c, int i, boolean doLevel, boolean doSystem, boolean doDefinition, List<UsedConceptMap> maps, CodeSystem allCS, List<String> langs) {
XhtmlNode tr = t.tr();
XhtmlNode td = tr.td();
String tgt = makeAnchor(c.getSystem(), c.getCode());
td.an(tgt);
if (doLevel) {
td.addText(Integer.toString(i));
td = tr.td();
}
String s = Utilities.padLeft("", '\u00A0', i*2);
td.attribute("style", "white-space:nowrap").addText(s);
addCodeToTable(c.getAbstract(), c.getSystem(), c.getCode(), c.getDisplay(), td);
if (doSystem) {
td = tr.td();
td.addText(c.getSystem());
}
td = tr.td();
if (c.hasDisplayElement())
td.addText(c.getDisplay());
if (doDefinition) {
CodeSystem cs = allCS;
if (cs == null)
cs = context.fetchCodeSystem(c.getSystem());
td = tr.td();
if (cs != null)
td.addText(CodeSystemUtilities.getCodeDefinition(cs, c.getCode()));
}
for (UsedConceptMap m : maps) {
td = tr.td();
List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap());
boolean first = true;
for (TargetElementComponentWrapper mapping : mappings) {
if (!first)
td.br();
first = false;
XhtmlNode span = td.span(null, mapping.comp.getRelationship().toString());
span.addText(getCharForRelationship(mapping.comp));
addRefToCode(td, mapping.group.getTarget(), m.getLink(), mapping.comp.getCode());
if (!Utilities.noString(mapping.comp.getComment()))
td.i().tx("("+mapping.comp.getComment()+")");
}
}
for (Extension ext : c.getExtension()) {
if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) {
String lang = ToolingExtensions.readStringExtension(ext, "lang");
if (!Utilities.noString(lang) && !langs.contains(lang))
langs.add(lang);
}
}
for (ValueSetExpansionContainsComponent cc : c.getContains()) {
addExpansionRowToTable(t, cc, i+1, doLevel, doSystem, doDefinition, maps, allCS, langs);
}
}
private boolean checkSystemMatches(String system, ValueSetExpansionContainsComponent cc) {
if (!system.equals(cc.getSystem()))
return false;
for (ValueSetExpansionContainsComponent cc1 : cc.getContains()) {
if (!checkSystemMatches(system, cc1))
return false;
}
return true;
}
private void addCodeToTable(boolean isAbstract, String system, String code, String display, XhtmlNode td) {
CodeSystem e = context.fetchCodeSystem(system);
if (e == null || e.getContent() != org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode.COMPLETE) {
if (isAbstract)
td.i().setAttribute("title", ABSTRACT_CODE_HINT).addText(code);
else if ("http://snomed.info/sct".equals(system)) {
td.ah(sctLink(code)).addText(code);
} else if ("http://loinc.org".equals(system)) {
td.ah("http://details.loinc.org/LOINC/"+code+".html").addText(code);
} else
td.addText(code);
} else {
String href = prefix+getCsRef(e);
if (href.contains("#"))
href = href + "-"+Utilities.nmtokenize(code);
else
href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(code);
if (isAbstract)
td.ah(href).setAttribute("title", ABSTRACT_CODE_HINT).i().addText(code);
else
td.ah(href).addText(code);
}
}
public String sctLink(String code) {
// if (snomedEdition != null)
// http://browser.ihtsdotools.org/?perspective=full&conceptId1=428041000124106&edition=us-edition&release=v20180301&server=https://prod-browser-exten.ihtsdotools.org/api/snomed&langRefset=900000000000509007
return "http://browser.ihtsdotools.org/?perspective=full&conceptId1="+code;
}
private void addRefToCode(XhtmlNode td, String target, String vslink, String code) {
CodeSystem cs = context.fetchCodeSystem(target);
String cslink = getCsRef(cs);
XhtmlNode a = null;
if (cslink != null)
a = td.ah(prefix+cslink+"#"+cs.getId()+"-"+code);
else
a = td.ah(prefix+vslink+"#"+code);
a.addText(code);
}
private boolean generateComposition(ResourceContext rcontext, XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRException, IOException {
boolean hasExtensions = false;
List<String> langs = new ArrayList<String>();
if (header) {
XhtmlNode h = x.h2();
h.addText(vs.present());
addMarkdown(x, vs.getDescription());
if (vs.hasCopyrightElement())
generateCopyright(x, vs);
}
XhtmlNode ul = x.ul();
if (vs.getCompose().getInclude().size() == 1 && vs.getCompose().getExclude().size() == 0) {
hasExtensions = genInclude(rcontext, ul, vs.getCompose().getInclude().get(0), "Include", langs, maps) || hasExtensions;
} else {
XhtmlNode p = x.para();
p.tx("This value set includes codes based on the following rules:");
XhtmlNode li;
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
hasExtensions = genInclude(rcontext, ul, inc, "Include", langs, maps) || hasExtensions;
}
for (ConceptSetComponent exc : vs.getCompose().getExclude()) {
hasExtensions = genInclude(rcontext, ul, exc, "Exclude", langs, maps) || hasExtensions;
}
}
// now, build observed languages
if (langs.size() > 0) {
Collections.sort(langs);
x.para().b().tx("Additional Language Displays");
XhtmlNode t = x.table( "codes");
XhtmlNode tr = t.tr();
tr.td().b().tx("Code");
for (String lang : langs)
tr.td().b().addText(describeLang(lang));
for (ConceptSetComponent c : vs.getCompose().getInclude()) {
for (ConceptReferenceComponent cc : c.getConcept()) {
addLanguageRow(cc, t, langs);
}
}
}
return hasExtensions;
}
private boolean genInclude(ResourceContext rcontext, XhtmlNode ul, ConceptSetComponent inc, String type, List<String> langs, List<UsedConceptMap> maps) throws FHIRException, IOException {
boolean hasExtensions = false;
XhtmlNode li;
li = ul.li();
CodeSystem e = context.fetchCodeSystem(inc.getSystem());
if (inc.hasSystem()) {
if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
li.addText(type+" all codes defined in ");
addCsRef(inc, li, e);
} else {
if (inc.getConcept().size() > 0) {
li.addText(type+" these codes as defined in ");
addCsRef(inc, li, e);
if (inc.hasVersion()) {
li.addText(" version ");
li.code(inc.getVersion());
}
XhtmlNode t = li.table("none");
boolean hasComments = false;
boolean hasDefinition = false;
for (ConceptReferenceComponent c : inc.getConcept()) {
hasComments = hasComments || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT);
hasDefinition = hasDefinition || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION);
}
if (hasComments || hasDefinition)
hasExtensions = true;
addMapHeaders(addTableHeaderRowStandard(t, false, true, hasDefinition, hasComments, false, false, null), maps);
for (ConceptReferenceComponent c : inc.getConcept()) {
XhtmlNode tr = t.tr();
XhtmlNode td = tr.td();
ConceptDefinitionComponent cc = getConceptForCode(e, c.getCode(), inc);
addCodeToTable(false, inc.getSystem(), c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td);
td = tr.td();
if (!Utilities.noString(c.getDisplay()))
td.addText(c.getDisplay());
else if (cc != null && !Utilities.noString(cc.getDisplay()))
td.addText(cc.getDisplay());
td = tr.td();
if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION))
smartAddText(td, ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_DEFINITION));
else if (cc != null && !Utilities.noString(cc.getDefinition()))
smartAddText(td, cc.getDefinition());
if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT)) {
smartAddText(tr.td(), "Note: "+ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_VS_COMMENT));
}
for (ConceptReferenceDesignationComponent cd : c.getDesignation()) {
if (cd.hasLanguage() && !langs.contains(cd.getLanguage()))
langs.add(cd.getLanguage());
}
}
}
if (inc.getFilter().size() > 0) {
li.addText(type+" codes from ");
addCsRef(inc, li, e);
li.tx(" where ");
for (int i = 0; i < inc.getFilter().size(); i++) {
ConceptSetFilterComponent f = inc.getFilter().get(i);
if (i > 0) {
if (i == inc.getFilter().size()-1) {
li.tx(" and ");
} else {
li.tx(", ");
}
}
if (f.getOp() == FilterOperator.EXISTS) {
if (f.getValue().equals("true")) {
li.tx(f.getProperty()+" exists");
} else {
li.tx(f.getProperty()+" doesn't exist");
}
} else {
li.tx(f.getProperty()+" "+describe(f.getOp())+" ");
if (e != null && codeExistsInValueSet(e, f.getValue())) {
String href = prefix+getCsRef(e);
if (href.contains("#"))
href = href + "-"+Utilities.nmtokenize(f.getValue());
else
href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue());
li.ah(href).addText(f.getValue());
} else if ("concept".equals(f.getProperty()) && inc.hasSystem()) {
li.addText(f.getValue());
ValidationResult vr = context.validateCode(terminologyServiceOptions, inc.getSystem(), f.getValue(), null);
if (vr.isOk()) {
li.tx(" ("+vr.getDisplay()+")");
}
}
else
li.addText(f.getValue());
String disp = ToolingExtensions.getDisplayHint(f);
if (disp != null)
li.tx(" ("+disp+")");
}
}
}
}
if (inc.hasValueSet()) {
li.tx(", where the codes are contained in ");
boolean first = true;
for (UriType vs : inc.getValueSet()) {
if (first)
first = false;
else
li.tx(", ");
AddVsRef(rcontext, vs.asStringValue(), li);
}
}
} else {
li.tx("Import all the codes that are contained in ");
boolean first = true;
for (UriType vs : inc.getValueSet()) {
if (first)
first = false;
else
li.tx(", ");
AddVsRef(rcontext, vs.asStringValue(), li);
}
}
return hasExtensions;
}
private ConceptDefinitionComponent getConceptForCode(CodeSystem e, String code, ConceptSetComponent inc) {
if (code == null) {
return null;
}
// first, look in the code systems
if (e == null)
e = context.fetchCodeSystem(inc.getSystem());
if (e != null) {
ConceptDefinitionComponent v = getConceptForCode(e.getConcept(), code);
if (v != null)
return v;
}
if (noSlowLookup)
return null;
if (!context.hasCache()) {
ValueSetExpansionComponent vse;
try {
ValueSetExpansionOutcome vso = context.expandVS(inc, false);
ValueSet valueset = vso.getValueset();
if (valueset == null)
throw new TerminologyServiceException("Error Expanding ValueSet: "+vso.getError());
vse = valueset.getExpansion();
} catch (TerminologyServiceException e1) {
return null;
}
if (vse != null) {
ConceptDefinitionComponent v = getConceptForCodeFromExpansion(vse.getContains(), code);
if (v != null)
return v;
}
}
return context.validateCode(terminologyServiceOptions, inc.getSystem(), code, null).asConceptDefinition();
}
private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> list, String code) {
for (ConceptDefinitionComponent c : list) {
if (code.equals(c.getCode()))
return c;
ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);
if (v != null)
return v;
}
return null;
}
private ConceptDefinitionComponent getConceptForCodeFromExpansion(List<ValueSetExpansionContainsComponent> list, String code) {
for (ValueSetExpansionContainsComponent c : list) {
if (code.equals(c.getCode())) {
ConceptDefinitionComponent res = new ConceptDefinitionComponent();
res.setCode(c.getCode());
res.setDisplay(c.getDisplay());
return res;
}
ConceptDefinitionComponent v = getConceptForCodeFromExpansion(c.getContains(), code);
if (v != null)
return v;
}
return null;
}
private boolean codeExistsInValueSet(CodeSystem cs, String code) {
for (ConceptDefinitionComponent c : cs.getConcept()) {
if (inConcept(code, c))
return true;
}
return false;
}
private void addLanguageRow(ConceptReferenceComponent c, XhtmlNode t, List<String> langs) {
XhtmlNode tr = t.tr();
tr.td().addText(c.getCode());
for (String lang : langs) {
String d = null;
for (ConceptReferenceDesignationComponent cd : c.getDesignation()) {
String l = cd.getLanguage();
if (lang.equals(l))
d = cd.getValue();
}
tr.td().addText(d == null ? "" : d);
}
}
private String describe(FilterOperator op) {
if (op == null)
return " null ";
switch (op) {
case EQUAL: return " = ";
case ISA: return " is-a ";
case ISNOTA: return " is-not-a ";
case REGEX: return " matches (by regex) ";
case NULL: return " ?ngen-13? ";
case IN: return " in ";
case NOTIN: return " not in ";
case DESCENDENTOF: return " descends from ";
case EXISTS: return " exists ";
case GENERALIZES: return " generalizes ";
}
return null;
}
private boolean inConcept(String code, ConceptDefinitionComponent c) {
if (c.hasCodeElement() && c.getCode().equals(code))
return true;
for (ConceptDefinitionComponent g : c.getConcept()) {
if (inConcept(code, g))
return true;
}
return false;
}
}

View File

@ -102,6 +102,7 @@ import org.hl7.fhir.r5.model.TypeDetails.ProfiledType;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.terminologies.TerminologyRenderer;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
@ -2612,7 +2613,7 @@ public class StructureMapUtilities {
if (Utilities.noString(code))
throw new FHIRException("Describe Transform, but the code is blank");
Coding c = buildCoding(uri, code);
return NarrativeGenerator.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : "");
return TerminologyRenderer.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : "");
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.18-SNAPSHOT</version>
<version>4.2.19-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -419,6 +419,7 @@ public class I18nConstants {
public final static String UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER_USE_PARAMETER_TX_NA_TUN_RUN_WITHOUT_USING_TERMINOLOGY_SERVICES_TO_VALIDATE_LOINC_SNOMED_ICDX_ETC_ERROR__ = "Unable_to_connect_to_terminology_server_Use_parameter_tx_na_tun_run_without_using_terminology_services_to_validate_LOINC_SNOMED_ICDX_etc_Error__";
public final static String DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF_ = "Display_Name_for__should_be_one_of__instead_of_";
public final static String UNKNOWN_CODE__IN_ = "Unknown_Code__in_";
public final static String UNKNOWN_CODE__IN_FRAGMENT = "UNKNOWN_CODE__IN_FRAGMENT";
public final static String CODE_FOUND_IN_EXPANSION_HOWEVER_ = "Code_found_in_expansion_however_";
public final static String NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_ = "None_of_the_provided_codes_are_in_the_value_set_";
public final static String CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE = "Coding_has_no_system__cannot_validate";
@ -504,4 +505,6 @@ public class I18nConstants {
//public static final String
public static final String XHTML_URL_EMPTY = "XHTML_URL_EMPTY";
public static final String XHTML_URL_INVALID_CHARS = "XHTML_URL_INVALID_CHARS";
public static final String TERMINOLOGY_TX_SYSTEM_HTTPS = "TERMINOLOGY_TX_SYSTEM_HTTPS";
public static final String CODESYSTEM_CS_NO_VS_NOTCOMPLETE = "CODESYSTEM_CS_NO_VS_NOTCOMPLETE";
}

View File

@ -419,6 +419,7 @@ Error_parsing_ = Error parsing {0}:{1}
Unable_to_connect_to_terminology_server_Use_parameter_tx_na_tun_run_without_using_terminology_services_to_validate_LOINC_SNOMED_ICDX_etc_Error__ = Unable to connect to terminology server. Use parameter ''-tx n/a'' tun run without using terminology services to validate LOINC, SNOMED, ICD-X etc. Error = {0}
Display_Name_for__should_be_one_of__instead_of_ = Display Name for {0}#{1} should be one of ''{2}'' instead of ''{3}''
Unknown_Code__in_ = Unknown Code {0} in {1}
UNKNOWN_CODE__IN_FRAGMENT = Unknown Code {0} in {1} - note that the code system is labelled as a fragment, so the code may be valid in some other fragment
Code_found_in_expansion_however_ = Code found in expansion, however: {0}
None_of_the_provided_codes_are_in_the_value_set_ = None of the provided codes are in the value set {0}
Coding_has_no_system__cannot_validate = Coding has no system - cannot validate
@ -481,4 +482,7 @@ MEASURE_M_CRITERIA_CQL_NO_ELM = Error in {0}: No compiled version of CQL found
MEASURE_M_CRITERIA_CQL_ELM_NOT_VALID = = Error in {0}: Compiled version of CQL is not valid
MEASURE_M_CRITERIA_CQL_NOT_FOUND = The function {1} does not exist in the library {0}
XHTML_URL_EMPTY = URL is empty
XHTML_URL_INVALID_CHARS = URL contains Invalid Characters ({0})
XHTML_URL_INVALID_CHARS = URL contains Invalid Characters ({0})
TERMINOLOGY_TX_SYSTEM_HTTPS = The system URL ''{0}'' wrongly starts with https: not http:
CODESYSTEM_CS_NO_VS_NOTCOMPLETE = Review the All Codes Value Set - incomplete CodeSystems generally should not have an all codes value set specified

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.18-SNAPSHOT</version>
<version>4.2.19-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>4.2.18-SNAPSHOT</version>
<version>4.2.19-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -243,6 +243,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
private Set<String> loadedIgs = new HashSet<>();
private IValidatorResourceFetcher fetcher;
private boolean assumeValidRestReferences;
private boolean noExtensibleBindingMessages;
private Locale locale;
private class AsteriskFilter implements FilenameFilter {
@ -1272,6 +1273,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
validator.setNoInvariantChecks(isNoInvariantChecks());
validator.setValidationLanguage(language);
validator.setAssumeValidRestReferences(assumeValidRestReferences);
validator.setNoExtensibleWarnings(noExtensibleBindingMessages);
validator.getContext().setLocale(locale);
validator.setFetcher(this);
return validator;
@ -1701,6 +1703,14 @@ public class ValidationEngine implements IValidatorResourceFetcher {
this.assumeValidRestReferences = assumeValidRestReferences;
}
public boolean isNoExtensibleBindingMessages() {
return noExtensibleBindingMessages;
}
public void setNoExtensibleBindingMessages(boolean noExtensibleBindingMessages) {
this.noExtensibleBindingMessages = noExtensibleBindingMessages;
}
public byte[] transformVersion(String source, String targetVer, FhirFormat format, Boolean canDoNative) throws FHIRException, IOException, Exception {
Content cnt = loadContent(source, "validate");
org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);

View File

@ -32,6 +32,8 @@ public class CliContext {
private String snomedCT = SnomedVersion.INTL.getCode();
private String targetVer = null;
private boolean doDebug = false;
private boolean noInternalCaching = false; // internal, for when debugging terminology validation
private boolean noExtensibleBindingMessages = false;
private boolean assumeValidRestReferences = false;
public String getMap() {
@ -286,4 +288,24 @@ public class CliContext {
this.assumeValidRestReferences = assumeValidRestReferences;
return this;
}
public boolean isNoInternalCaching() {
return noInternalCaching;
}
public CliContext setNoInternalCaching(boolean noInternalCaching) {
this.noInternalCaching = noInternalCaching;
return this;
}
public boolean isNoExtensibleBindingMessages() {
return noExtensibleBindingMessages;
}
public CliContext setNoExtensibleBindingMessages(boolean noExtensibleBindingMessages) {
this.noExtensibleBindingMessages = noExtensibleBindingMessages;
return this;
}
}

View File

@ -45,6 +45,8 @@ public class Params {
public static final String DESTINATION = "-dest";
public static final String LEFT = "-left";
public static final String RIGHT = "-right";
public static final String NO_INTERNAL_CACHING = "-no-internal-caching";
public static final String NO_EXTENSIBLE_BINDING_WARNINGS = "-no-extensible-binding-warnings";
/**
* Checks the list of passed in params to see if it contains the passed in param.
@ -90,18 +92,19 @@ public class Params {
i++; // ignore next parameter
} else if (args[i].equals(PROFILE)) {
String p = null;
if (i + 1 == args.length)
if (i + 1 == args.length) {
throw new Error("Specified -profile without indicating profile source");
else {
} else {
p = args[++i];
cliContext.addProfile(args[++i]);
cliContext.addProfile(args[i++]);
}
if (p != null && i + 1 < args.length && args[i + 1].equals("@")) {
i++;
if (i + 1 == args.length)
if (i + 1 == args.length) {
throw new Error("Specified -profile with @ without indicating profile location");
else
} else {
cliContext.addLocation(p, args[++i]);
}
}
} else if (args[i].equals(QUESTIONNAIRE)) {
if (i + 1 == args.length)
@ -126,6 +129,10 @@ public class Params {
}
} else if (args[i].equals(STRICT_EXTENSIONS)) {
cliContext.setAnyExtensionsAllowed(false);
} else if (args[i].equals(NO_INTERNAL_CACHING)) {
cliContext.setNoInternalCaching(true);
} else if (args[i].equals(NO_EXTENSIBLE_BINDING_WARNINGS)) {
cliContext.setNoExtensibleBindingMessages(true);
} else if (args[i].equals(HINT_ABOUT_NON_MUST_SUPPORT)) {
cliContext.setHintAboutNonMustSupport(true);
} else if (args[i].equals(TO_VERSION)) {

View File

@ -1,5 +1,6 @@
package org.hl7.fhir.validation.cli;
import org.hl7.fhir.r5.context.TerminologyCache;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.formats.JsonParser;
@ -155,6 +156,8 @@ public class ValidationUtils {
validator.setLocale(cliContext.getLocale());
validator.setSnomedExtension(cliContext.getSnomedCT());
validator.setAssumeValidRestReferences(cliContext.isAssumeValidRestReferences());
validator.setNoExtensibleBindingMessages(cliContext.isNoExtensibleBindingMessages());
TerminologyCache.setNoCaching(cliContext.isNoInternalCaching());
return validator;
}

View File

@ -139,6 +139,7 @@ import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.EnableWhenEvaluator.QStack;
import org.hl7.fhir.validation.instance.type.CodeSystemValidator;
import org.hl7.fhir.validation.instance.type.MeasureValidator;
import org.hl7.fhir.validation.instance.type.QuestionnaireValidator;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
@ -806,7 +807,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET, system);
// Lloyd: This error used to prohibit checking for downstream issues, but there are some cases where that checking needs to occur. Please talk to me before changing the code back.
}
hint(errors, IssueType.UNKNOWN, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system);
boolean done = false;
if (system.startsWith("https:") && system.length() > 7) {
String ns = "http:"+system.substring(6);
CodeSystem cs = getCodeSystem(ns);
if (cs != null || Utilities.existsInList(system, "https://loinc.org", "https://unitsofmeasure.org", "https://snomed.info/sct", "https://www.nlm.nih.gov/research/umls/rxnorm")) {
rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_HTTPS, system);
done = true;
}
}
hint(errors, IssueType.UNKNOWN, element.line(), element.col(), path, done, I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system);
return true;
} catch (Exception e) {
return true;
@ -1613,6 +1623,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if ("http://hl7.org/fhir/StructureDefinition/regex".equals(extUrl)) {
list.get(1).setExpression("ElementDefinition.type");
}
if ("http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version".equals(extUrl)) {
list.get(0).setExpression("Element"); // well, it can't be used anywhere but the list of places it can be used is quite long
}
return list;
}
@ -3466,7 +3479,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (element.getType().equals("CapabilityStatement")) {
validateCapabilityStatement(errors, element, stack);
} else if (element.getType().equals("CodeSystem")) {
validateCodeSystem(errors, element, stack);
new CodeSystemValidator(context, timeTracker).validateCodeSystem(errors, element, stack);
}
}
@ -3550,28 +3563,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
private void validateCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack) {
String url = cs.getNamedChildValue("url");
String vsu = cs.getNamedChildValue("valueSet");
if (!Utilities.noString(vsu)) {
ValueSet vs;
try {
vs = context.fetchResourceWithException(ValueSet.class, vsu);
} catch (FHIRException e) {
vs = null;
}
if (vs != null) {
if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.hasCompose() && !vs.hasExpansion(), I18nConstants.CODESYSTEM_CS_VS_MISMATCH, url, vsu))
if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getCompose().getInclude().size() == 1, I18nConstants.CODESYSTEM_CS_VS_INVALID, url, vsu))
if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getCompose().getInclude().get(0).getSystem().equals(url), I18nConstants.CODESYSTEM_CS_VS_WRONGSYSTEM, url, vsu, vs.getCompose().getInclude().get(0).getSystem())) {
rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !vs.getCompose().getInclude().get(0).hasValueSet()
&& !vs.getCompose().getInclude().get(0).hasConcept() && !vs.getCompose().getInclude().get(0).hasFilter(), I18nConstants.CODESYSTEM_CS_VS_INCLUDEDETAILS, url, vsu);
}
}
} // todo... try getting the value set the other way...
}
private void validateBundle(List<ValidationMessage> errors, Element bundle, NodeStack stack, boolean checkSpecials) {
List<Element> entries = new ArrayList<Element>();
bundle.getNamedChildren(ENTRY, entries);

View File

@ -0,0 +1,51 @@
package org.hl7.fhir.validation.instance.type;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.utilities.Utilities;
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.validation.BaseValidator;
import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.utils.NodeStack;
public class CodeSystemValidator extends BaseValidator {
public CodeSystemValidator(IWorkerContext context, TimeTracker timeTracker) {
super(context);
source = Source.InstanceValidator;
this.timeTracker = timeTracker;
}
public void validateCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack) {
String url = cs.getNamedChildValue("url");
String content = cs.getNamedChildValue("content");
String vsu = cs.getNamedChildValue("valueSet");
if (!Utilities.noString(vsu)) {
hint(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), "complete".equals(content), I18nConstants.CODESYSTEM_CS_NO_VS_NOTCOMPLETE);
ValueSet vs;
try {
vs = context.fetchResourceWithException(ValueSet.class, vsu);
} catch (FHIRException e) {
vs = null;
}
if (vs != null) {
if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.hasCompose() && !vs.hasExpansion(), I18nConstants.CODESYSTEM_CS_VS_MISMATCH, url, vsu))
if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getCompose().getInclude().size() == 1, I18nConstants.CODESYSTEM_CS_VS_INVALID, url, vsu))
if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getCompose().getInclude().get(0).getSystem().equals(url), I18nConstants.CODESYSTEM_CS_VS_WRONGSYSTEM, url, vsu, vs.getCompose().getInclude().get(0).getSystem())) {
rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !vs.getCompose().getInclude().get(0).hasValueSet()
&& !vs.getCompose().getInclude().get(0).hasConcept() && !vs.getCompose().getInclude().get(0).hasFilter(), I18nConstants.CODESYSTEM_CS_VS_INCLUDEDETAILS, url, vsu);
}
}
} // todo... try getting the value set the other way...
}
}

View File

@ -3,10 +3,13 @@ package org.hl7.fhir.validation.tests;
import java.io.File;
import java.util.UUID;
import org.hl7.fhir.convertors.R5ToR5Loader;
import org.hl7.fhir.r5.conformance.ProfileComparer;
import org.hl7.fhir.r5.model.FhirPublication;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.utils.KeyGenerator;
import org.hl7.fhir.utilities.cache.PackageCacheManager;
import org.hl7.fhir.utilities.cache.ToolsVersion;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.tests.utilities.TestUtilities;
@ -22,7 +25,8 @@ public class ProfileComparisonTests {
System.out.println("Compare US Patient Core with AU Patient Base");
ValidationEngine ve = new ValidationEngine("hl7.fhir.r3.core#3.0.2", DEF_TX, null, FhirPublication.STU3, "3.0.2");
ve.loadIg("hl7.fhir.us.core#1.0.1", false);
ve.loadIg("hl7.fhir.au.base#dev", false);
ve.loadIg("hl7.fhir.au.base#current", false);
ve.getContext().loadFromPackage(new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION).loadPackage("hl7.fhir.pubpack", "0.0.4"), new R5ToR5Loader(new String[] {"Binary"}), "Binary");
String left = "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient";

View File

@ -13,7 +13,7 @@
each other. It is fine to bump the point version of this POM without affecting
HAPI FHIR.
-->
<version>4.2.18-SNAPSHOT</version>
<version>4.2.19-SNAPSHOT</version>
<properties>
<hapi_fhir_version>4.2.0</hapi_fhir_version>

View File

@ -1,7 +1,7 @@
@echo off
set oldver=4.2.17
set newver=4.2.18
set oldver=4.2.18
set newver=4.2.19
echo ..
echo =========================================================================