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