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}