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