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}