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