001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.util.ArrayList; 006import java.util.List; 007import java.util.Map; 008 009import org.hl7.fhir.exceptions.DefinitionException; 010import org.hl7.fhir.exceptions.FHIRFormatError; 011import org.hl7.fhir.r5.model.CanonicalResource; 012import org.hl7.fhir.r5.model.CodeSystem; 013import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 014import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent; 015import org.hl7.fhir.r5.model.ConceptMap; 016import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; 017import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent; 018import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent; 019import org.hl7.fhir.r5.model.Questionnaire; 020import org.hl7.fhir.r5.model.Resource; 021import org.hl7.fhir.r5.model.StructureDefinition; 022import org.hl7.fhir.r5.model.ValueSet; 023import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 024import org.hl7.fhir.r5.renderers.utils.RenderingContext; 025import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 026import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 027import org.hl7.fhir.r5.utils.ToolingExtensions; 028import org.hl7.fhir.utilities.CanonicalPair; 029import org.hl7.fhir.utilities.Utilities; 030import org.hl7.fhir.utilities.xhtml.XhtmlNode; 031 032public abstract class TerminologyRenderer extends ResourceRenderer { 033 034 035 036 public TerminologyRenderer(RenderingContext context) { 037 super(context); 038 } 039 040 public String display(Resource r) throws UnsupportedEncodingException, IOException { 041 return ((CanonicalResource) r).present(); 042 } 043 044 protected class TargetElementComponentWrapper { 045 protected ConceptMapGroupComponent group; 046 protected TargetElementComponent comp; 047 protected TargetElementComponentWrapper(ConceptMapGroupComponent group, TargetElementComponent comp) { 048 super(); 049 this.group = group; 050 this.comp = comp; 051 } 052 053 } 054 055 public class UsedConceptMap { 056 057 private ConceptMapRenderInstructions details; 058 private String link; 059 private ConceptMap map; 060 public UsedConceptMap(ConceptMapRenderInstructions details, String link, ConceptMap map) { 061 super(); 062 this.details = details; 063 this.link = link; 064 this.map = map; 065 } 066 public ConceptMapRenderInstructions getDetails() { 067 return details; 068 } 069 public ConceptMap getMap() { 070 return map; 071 } 072 public String getLink() { 073 return link; 074 } 075 } 076 077 public class ConceptMapRenderInstructions { 078 private String name; 079 private String url; 080 private boolean doDescription; 081 public ConceptMapRenderInstructions(String name, String url, boolean doDescription) { 082 super(); 083 this.name = name; 084 this.url = url; 085 this.doDescription = doDescription; 086 } 087 public String getName() { 088 return name; 089 } 090 public String getUrl() { 091 return url; 092 } 093 public boolean isDoDescription() { 094 return doDescription; 095 } 096 } 097 098 099 protected XhtmlNode addMapHeaders(XhtmlNode tr, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException { 100 for (UsedConceptMap m : maps) { 101 XhtmlNode td = tr.td(); 102 XhtmlNode b = td.b(); 103 String link = m.getLink(); 104 XhtmlNode a = b.ah(context.prefixLocalHref(link)); 105 a.addText(m.getDetails().getName()); 106 if (m.getDetails().isDoDescription() && m.getMap().hasDescription()) 107 addMarkdown(td, m.getMap().getDescription()); 108 } 109 return tr; 110 } 111 112 protected String getHeader() { 113 int i = 3; 114 while (i <= getContext().getHeaderLevelContext()) 115 i++; 116 if (i > 6) 117 i = 6; 118 return "h"+Integer.toString(i); 119 } 120 121 protected List<TargetElementComponentWrapper> findMappingsForCode(String code, ConceptMap map) { 122 List<TargetElementComponentWrapper> mappings = new ArrayList<TargetElementComponentWrapper>(); 123 124 for (ConceptMapGroupComponent g : map.getGroup()) { 125 for (SourceElementComponent c : g.getElement()) { 126 if (c.getCode().equals(code)) 127 for (TargetElementComponent cc : c.getTarget()) 128 mappings.add(new TargetElementComponentWrapper(g, cc)); 129 } 130 } 131 return mappings; 132 } 133 134 135 136 protected String getCharForRelationship(TargetElementComponent mapping) { 137 if (!mapping.hasRelationship()) 138 return ""; 139 switch (mapping.getRelationship()) { 140 case EQUIVALENT : return "~"; 141 case SOURCEISNARROWERTHANTARGET : return "<"; 142 case SOURCEISBROADERTHANTARGET : return ">"; 143 case NOTRELATEDTO : return "!="; 144 default: return "?"; 145 } 146 } 147 148 protected <T extends Resource> void addCsRef(ConceptSetComponent inc, XhtmlNode li, T cs) { 149 String ref = null; 150 boolean addHtml = true; 151 if (cs != null) { 152 ref = (String) cs.getUserData("external.url"); 153 if (Utilities.noString(ref)) 154 ref = (String) cs.getUserData("filename"); 155 else 156 addHtml = false; 157 if (Utilities.noString(ref)) { 158 ref = (String) cs.getWebPath(); 159 if (ref != null) { 160 addHtml = false; 161 } 162 } 163 } 164 String spec = getSpecialReference(inc.getSystem()); 165 if (spec != null) { 166 XhtmlNode a = li.ah(context.prefixLocalHref(spec)); 167 a.code(inc.getSystem()); 168 } else if (cs != null && ref != null) { 169 if (addHtml && !ref.contains(".html")) 170 ref = ref + ".html"; 171 ref = context.fixReference(ref); 172 XhtmlNode a = li.ah(context.prefixLocalHref(ref.replace("\\", "/"))); 173 a.code(inc.getSystem()); 174 } else { 175 li.code(inc.getSystem()); 176 } 177 } 178 179 180 private String getSpecialReference(String system) { 181 if ("http://snomed.info/sct".equals(system)) 182 return "http://www.snomed.org/"; 183 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", 184 "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", 185 "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", 186 "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/")) 187 return system; 188 189 return null; 190 } 191 192 protected XhtmlNode addTableHeaderRowStandard(XhtmlNode t, boolean hasHierarchy, boolean hasDisplay, boolean definitions, boolean comments, boolean version, boolean deprecated, List<PropertyComponent> properties, List<String> langs, Map<String, String> designations, boolean doDesignations) { 193 XhtmlNode tr = t.tr(); 194 if (hasHierarchy) { 195 tr.td().b().tx(context.formatPhrase(RenderingContext.TERMINOLOGY_LVL)); 196 } 197 tr.td().attribute("style", "white-space:nowrap").b().tx(formatPhrase(RenderingContext.GENERAL_CODE)); 198 if (hasDisplay) { 199 tr.td().b().tx(formatPhrase(RenderingContext.TX_DISPLAY)); 200 } 201 if (definitions) { 202 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_DEFINITION)); 203 } 204 if (deprecated) { 205 tr.td().b().tx(formatPhrase(RenderingContext.CODESYSTEM_DEPRECATED)); 206 } 207 if (comments) { 208 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_COMMENTS)); 209 } 210 if (version) { 211 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_VER)); 212 } 213 if (properties != null) { 214 for (PropertyComponent pc : properties) { 215 String display = getDisplayForProperty(pc); 216 tr.td().b().tx(display); 217 } 218 } 219 if (doDesignations) { 220 if (designations != null) { 221 for (String url : designations.keySet()) { 222 tr.td().b().addText(designations.get(url)); 223 } 224 } 225 if (langs != null) { 226 for (String lang : langs) { 227 tr.td().b().addText(describeLang(lang)); 228 } 229 } 230 } 231 return tr; 232 } 233 234 protected String getDisplayForProperty(PropertyComponent pc) { 235 String display = ToolingExtensions.getPresentation(pc, pc.getCodeElement()); 236 if (display == null || display.equals(pc.getCode()) && pc.hasUri()) { 237 display = getDisplayForProperty(pc.getUri()); 238 if (display == null) { 239 display = pc.getCode(); 240 } 241 } 242 return display; 243 } 244 245 246 protected String getDisplayForProperty(String uri) { 247 if (Utilities.noString(uri)){ 248 return null; 249 } 250 String code = null; 251 if (uri.contains("#")) { 252 code = uri.substring(uri.indexOf("#")+1); 253 uri = uri.substring(0, uri.indexOf("#")); 254 } 255 CodeSystem cs = getContext().getWorker().fetchCodeSystem(uri); 256 if (cs == null) { 257 return null; 258 } 259 ConceptDefinitionComponent cc = code == null ? null : CodeSystemUtilities.getCode(cs, code); 260 return cc == null ? null : cc.getDisplay(); 261 } 262 263 264 protected void AddVsRef(String value, XhtmlNode li, Resource source) { 265 Resource res = null; 266 if (res != null && !(res instanceof CanonicalResource)) { 267 li.addText(value); 268 return; 269 } 270 CanonicalResource vs = (CanonicalResource) res; 271 if (vs == null) 272 vs = getContext().getWorker().findTxResource(ValueSet.class, value, source); 273 if (vs == null) 274 vs = getContext().getWorker().fetchResource(StructureDefinition.class, value, source); 275 if (vs == null) 276 vs = getContext().getWorker().fetchResource(Questionnaire.class, value, source); 277 if (vs != null) { 278 String ref = (String) vs.getWebPath(); 279 280 ref = context.fixReference(ref); 281 XhtmlNode a = li.ah(context.prefixLocalHref(ref == null ? "?ngen-11?" : ref.replace("\\", "/"))); 282 a.addText(vs.present()); 283 } else { 284 CodeSystem cs = getContext().getWorker().fetchCodeSystem(value); 285 if (cs != null) { 286 String ref = (String) cs.getWebPath(); 287 ref = context.fixReference(ref); 288 XhtmlNode a = li.ah(context.prefixLocalHref(ref == null ? "?ngen-12?" : ref.replace("\\", "/"))); 289 a.addText(value); 290 } else if (value.equals("http://snomed.info/sct") || value.equals("http://snomed.info/id")) { 291 XhtmlNode a = li.ah(context.prefixLocalHref(value)); 292 a.tx(context.formatPhrase(RenderingContext.STRUC_DEF_SNOMED)); 293 } 294 else { 295 if (value.startsWith("http://hl7.org") && !Utilities.existsInList(value, "http://hl7.org/fhir/sid/icd-10-us")) { 296 if (DEBUG) { 297 System.out.println("Unable to resolve value set "+value); 298 } 299 } 300 li.addText(value); 301 } 302 } 303 } 304 305 protected String getDisplayForConcept(String canonical, String value) { 306 var split = CanonicalPair.of(canonical); 307 return getDisplayForConcept(split.getUrl(), split.getVersion(), value); 308 } 309 310 protected String getDisplayForConcept(String system, String version, String value) { 311 if (value == null || system == null) 312 return null; 313 ValidationResult cl = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions().withVersionFlexible(true), system, version, value, null); 314 return cl == null ? null : cl.getDisplay(); 315 } 316 317 318 protected void clipboard(XhtmlNode x, String img, String title, String source) { 319 XhtmlNode span = x.span("cursor: pointer", formatPhrase(RenderingContext.TERM_REND_COPY, title)); 320 span.attribute("onClick", "navigator.clipboard.writeText('"+Utilities.escapeJson(source)+"');"); 321 span.img(img, "btn").setAttribute("width", "24px").setAttribute("height", "16px"); 322 } 323 324 325 326}