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