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}