001package org.hl7.fhir.r5.renderers;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.List;
007import java.util.Map;
008
009import org.hl7.fhir.exceptions.DefinitionException;
010import org.hl7.fhir.exceptions.FHIRException;
011import org.hl7.fhir.exceptions.FHIRFormatError;
012import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation;
013import org.hl7.fhir.r5.model.BooleanType;
014import org.hl7.fhir.r5.model.CanonicalResource;
015import org.hl7.fhir.r5.model.CodeSystem;
016import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
017import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent;
018import org.hl7.fhir.r5.model.CodeSystem.CodeSystemHierarchyMeaning;
019import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
020import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
021import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
022import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
023import org.hl7.fhir.r5.model.Coding;
024import org.hl7.fhir.r5.model.Enumeration;
025import org.hl7.fhir.r5.model.Extension;
026import org.hl7.fhir.r5.model.PrimitiveType;
027import org.hl7.fhir.r5.model.Resource;
028import org.hl7.fhir.r5.model.StringType;
029import org.hl7.fhir.r5.renderers.CodeSystemRenderer.Translateable;
030import org.hl7.fhir.r5.renderers.utils.RenderingContext;
031import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType;
032import org.hl7.fhir.r5.renderers.utils.RenderingContext.MultiLanguagePolicy;
033import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext;
034import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
035import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.CodeSystemNavigator;
036import org.hl7.fhir.r5.utils.ToolingExtensions;
037import org.hl7.fhir.utilities.LoincLinker;
038import org.hl7.fhir.utilities.Utilities;
039import org.hl7.fhir.utilities.i18n.I18nConstants;
040import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
041import org.hl7.fhir.utilities.xhtml.XhtmlNode;
042
043public class CodeSystemRenderer extends TerminologyRenderer {
044
045  public class Translateable {
046
047    private String lang;
048    private StringType value;
049
050    public Translateable(String lang, StringType value) {
051      this.lang = lang;
052      this.value = value;
053    }
054
055    public String getLang() {
056      return lang;
057    }
058
059    public StringType getValue() {
060      return value;
061    }
062
063  }
064
065
066  private Boolean doMarkdown = null;
067
068  public CodeSystemRenderer(RenderingContext context) {
069    super(context);
070  }
071
072  public CodeSystemRenderer(RenderingContext context, ResourceContext rcontext) {
073    super(context, rcontext);
074  }
075  
076
077  public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException {
078    return render(x, (CodeSystem) dr);
079  }
080  
081  public boolean render(XhtmlNode x, CodeSystem cs) throws FHIRFormatError, DefinitionException, IOException {
082    boolean hasExtensions = false;
083
084    if (context.isHeader()) {
085      XhtmlNode h = x.h2();
086      h.addText(cs.hasTitle() ? cs.getTitle() : cs.getName());
087      addMarkdown(x, cs.getDescription());
088      if (cs.hasCopyright())
089        generateCopyright(x, cs );
090    }
091
092    boolean props = generateProperties(x, cs);
093    generateFilters(x, cs);
094    List<UsedConceptMap> maps = new ArrayList<UsedConceptMap>();
095    hasExtensions = generateCodeSystemContent(x, cs, hasExtensions, maps, props);
096
097    return hasExtensions;
098  }
099
100  public void describe(XhtmlNode x, CodeSystem cs) {
101    x.tx(display(cs));
102  }
103
104  public String display(CodeSystem cs) {
105    return cs.present();
106  }
107  
108  private void generateFilters(XhtmlNode x, CodeSystem cs) {
109    if (cs.hasFilter()) {
110      x.para().b().tx(formatPhrase(RenderingContext.CODESYSTEM_FILTERS));
111      XhtmlNode tbl = x.table("grid");
112      XhtmlNode tr = tbl.tr();
113      tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_CODE));
114      tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_DESC));
115      tr.td().b().tx(formatPhrase(RenderingContext.CODESYSTEM_FILTER_OP));
116      tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_VALUE));
117      for (CodeSystemFilterComponent f : cs.getFilter()) {
118        tr = tbl.tr();
119        renderStatus(f, tr.td()).tx(f.getCode());
120        renderStatus(f.getDescriptionElement(), tr.td()).tx(f.getDescription());
121        XhtmlNode td = tr.td();
122        for (Enumeration<org.hl7.fhir.r5.model.Enumerations.FilterOperator> t : f.getOperator())
123          renderStatus(t, td).tx(t.asStringValue()+" ");
124        renderStatus(f.getValueElement(), tr.td()).tx(f.getValue());
125      }
126    }
127  }
128
129  private boolean generateProperties(XhtmlNode x, CodeSystem cs) {
130    if (cs.hasProperty()) {
131      boolean hasRendered = false;
132      boolean hasURI = false;
133      boolean hasDescription = false;
134      for (PropertyComponent p : cs.getProperty()) {
135        hasRendered = hasRendered || getDisplayForProperty(p) != null;
136        hasURI = hasURI || p.hasUri();
137        hasDescription = hasDescription || p.hasDescription();
138      }
139      
140      x.para().b().tx(formatPhrase(RenderingContext.GENERAL_PROPS));
141      x.para().b().tx(formatPhrase(RenderingContext.CODESYSTEM_PROPS_DESC));
142      XhtmlNode tbl = x.table("grid");
143      XhtmlNode tr = tbl.tr();
144      if (hasRendered) {
145        tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_NAME));        
146      }
147      tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_CODE));
148      if (hasURI) {
149        tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_URI));
150      }
151      tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_TYPE));
152      if (hasDescription) {
153        tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_DESC));
154      }
155      for (PropertyComponent p : cs.getProperty()) {
156        tr = tbl.tr();
157        if (hasRendered) {
158          tr.td().tx(getDisplayForProperty(p));          
159        }
160        renderStatus(p, tr.td()).tx(p.getCode());
161        if (hasURI) {
162          renderStatus(p.getUriElement(), tr.td()).tx(p.getUri());
163        }
164        renderStatus(p.getTypeElement(), tr.td()).tx(p.hasType() ? p.getType().toCode() : "");
165        if (hasDescription) {
166          renderStatus(p.getDescriptionElement(), tr.td()).tx(p.getDescription());
167        }
168      }
169      return true;
170    } else {
171      return false;
172    }
173  }
174
175  private String sentenceForContent(CodeSystemContentMode mode, CodeSystem cs) {
176    if (mode == null) {
177      return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_NOTPRESENT);
178    }
179    switch (mode) {
180    case COMPLETE: return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_COMPLETE);
181    case EXAMPLE: return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_EXAMPLE);
182    case FRAGMENT: return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_FRAGMENT);
183    case NOTPRESENT: return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_NOTPRESENT);
184    case SUPPLEMENT:
185      boolean properties = CodeSystemUtilities.hasProperties(cs);
186      boolean designations = CodeSystemUtilities.hasDesignations(cs); 
187      String features;
188      if (properties && designations) {
189        features = (context.formatPhrase(RenderingContext.CODE_SYS_DISP_PROP));
190      } else if (properties) {
191        features = (context.formatPhrase(RenderingContext.CODE_SYS_PROP));
192      } else if (designations) {
193        features = (context.formatPhrase(RenderingContext.CODE_SYS_DISP));
194      } else {
195        features = (context.formatPhrase(RenderingContext.CODE_SYS_FEAT)); // ?
196      }
197      return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_SUPPLEMENT, features);
198    default:
199      throw new FHIRException(context.formatPhrase(RenderingContext.CODE_SYS_UNKN_MODE));
200    }
201  }
202  
203  private boolean generateCodeSystemContent(XhtmlNode x, CodeSystem cs, boolean hasExtensions, List<UsedConceptMap> maps, boolean props) throws FHIRFormatError, DefinitionException, IOException {
204    if (props) {
205      x.para().b().tx(formatPhrase(RenderingContext.CODESYSTEM_CONCEPTS));
206    }
207    XhtmlNode p = x.para();
208    renderStatus(cs.getUrlElement(), p.param("cs")).code().tx(cs.getUrl());
209    makeCasedParam(p.param("cased"), cs, cs.getCaseSensitiveElement());
210    makeHierarchyParam(p.param("h"), cs, cs.getHierarchyMeaningElement());
211
212    p.paramValue("code-count", CodeSystemUtilities.countCodes(cs));
213    p.sentenceForParams(sentenceForContent(cs.getContent(), cs));
214    if (cs.getContent() == CodeSystemContentMode.NOTPRESENT) {
215      return false;
216    }
217    
218    XhtmlNode t = x.table( "codes");
219    boolean definitions = false;
220    boolean commentS = false;
221    boolean deprecated = false;
222    boolean display = false;
223    boolean hierarchy = false;
224    boolean version = false;
225    boolean ignoreStatus = false;
226    boolean isSupplement = cs.getContent() == CodeSystemContentMode.SUPPLEMENT;
227    List<PropertyComponent> properties = new ArrayList<>();
228    for (PropertyComponent cp : cs.getProperty()) {
229      if (showPropertyInTable(cp)) {
230        boolean exists = false;
231        for (ConceptDefinitionComponent c : cs.getConcept()) {
232          exists = exists || conceptsHaveProperty(c, cp);
233        }
234        if (exists) {
235          properties.add(cp);
236          if ("status".equals(cp.getCode())) {
237            ignoreStatus = true;
238          }
239        }
240      }
241    }
242    List<String> langs = new ArrayList<>();
243    for (ConceptDefinitionComponent c : cs.getConcept()) {
244      commentS = commentS || conceptsHaveComments(c);
245      deprecated = deprecated || conceptsHaveDeprecated(cs, c, ignoreStatus);
246      display = display || conceptsHaveDisplay(c);
247      version = version || conceptsHaveVersion(c);
248      hierarchy = hierarchy || c.hasConcept();
249      definitions = definitions || conceptsHaveDefinition(c);
250      listConceptLanguages(cs, c, langs);
251    }
252    CodeSystemNavigator csNav = new CodeSystemNavigator(cs);
253    hierarchy = hierarchy || csNav.isRestructure();
254    
255    if (langs.size() < 2) {
256      addCopyColumn(addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, definitions, commentS, version, deprecated, properties, langs, null, true), maps));
257    } else {
258      addCopyColumn(addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, definitions, commentS, version, deprecated, properties, null, null, false), maps));      
259    }
260    for (ConceptDefinitionComponent c : csNav.getConcepts(null)) {
261      hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, definitions, commentS, version, deprecated, maps, cs.getUrl(), cs, properties, csNav, langs.size() < 2 ? langs : null, isSupplement) || hasExtensions;
262    }
263    if (langs.size() >= 2) {
264      Collections.sort(langs);
265      x.para().b().tx(context.formatPhrase(RenderingContext.GENERAL_ADD_LANG));
266      t = x.table("codes");
267      XhtmlNode tr = t.tr();
268      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE));
269      for (String lang : langs)
270        tr.td().b().addText(describeLang(lang));
271      for (ConceptDefinitionComponent c : cs.getConcept()) {
272        addLanguageRow(c, t, langs);
273      }
274    }
275    return hasExtensions;
276  }
277
278  private void makeHierarchyParam(XhtmlNode x, CodeSystem cs, Enumeration<CodeSystemHierarchyMeaning> hm) {
279    if (hm.hasValue()) {
280      String s = hm.getValue().getDisplay();
281      renderStatus(hm, x).tx(" "+context.formatPhrase(RenderingContext.CODE_SYS_IN_A_HIERARCHY, s));
282    } else if (VersionComparisonAnnotation.hasDeleted(cs, "hierarchyMeaning")) {
283      makeHierarchyParam(x, null, (Enumeration<CodeSystemHierarchyMeaning>) VersionComparisonAnnotation.getDeleted(cs, "hierarchyMeaning").get(0));
284    } else if (CodeSystemUtilities.hasHierarchy(cs)) {
285      x.tx(" "+ (context.formatPhrase(RenderingContext.CODE_SYS_UNDEF_HIER)));
286    } else {
287      x.tx("");
288    }
289  }
290
291  private void makeCasedParam(XhtmlNode x, CodeSystem cs, BooleanType caseSensitiveElement) {
292    if (caseSensitiveElement.hasValue()) {
293      String s = caseSensitiveElement.getValue() == true? "case-sensitive" : "case-insensitive";
294      renderStatus(caseSensitiveElement, x).tx(s);
295    } else if (VersionComparisonAnnotation.hasDeleted(cs, "caseSensitive")) {
296      makeCasedParam(x, null, (BooleanType) VersionComparisonAnnotation.getDeleted(cs, "caseSensitive").get(0));
297    } else {
298      x.tx("");
299    }
300  }
301
302  private void listConceptLanguages(CodeSystem cs, ConceptDefinitionComponent c, List<String> langs) {
303    for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
304      if (cd.hasLanguage() && !langs.contains(cd.getLanguage()) && (!cs.hasLanguage() || !cs.getLanguage().equals(cd.getLanguage()))) {
305        langs.add(cd.getLanguage());
306      }
307    }
308
309    for (ConceptDefinitionComponent g : c.getConcept()) {
310      listConceptLanguages(cs, g, langs);
311    }
312  }
313
314  private void addCopyColumn(XhtmlNode tr) {
315    if (context.isCopyButton()) {
316      tr.td().b().tx(context.formatPhrase(RenderingContext.CODE_SYS_COPY));
317    }
318    
319  }
320
321  private boolean conceptsHaveDefinition(ConceptDefinitionComponent c) {
322    if (c.hasDefinition()) {
323      return true;
324    }
325    for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 
326      if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) {
327        return true;
328      }
329    }
330    for (ConceptDefinitionComponent g : c.getConcept()) {
331      if (conceptsHaveDefinition(g)) {
332        return true;
333      }
334    }
335    return false;
336  }
337
338  private boolean conceptsHaveProperty(ConceptDefinitionComponent c, PropertyComponent cp) {
339    if (CodeSystemUtilities.hasProperty(c, cp.getCode()))
340      return true;
341    for (ConceptDefinitionComponent g : c.getConcept())
342      if (conceptsHaveProperty(g,  cp))
343        return true;
344    return false;
345
346  }
347
348  private boolean showPropertyInTable(PropertyComponent cp) {
349    if (cp.hasCode()) {
350      if (cp.hasExtension(ToolingExtensions.EXT_RENDERED_VALUE)) {
351        return true;
352      }
353      if (cp.getCodeElement().hasExtension(ToolingExtensions.EXT_RENDERED_VALUE)) {
354        return true;
355      }
356      String uri = cp.getUri();
357      if (Utilities.noString(uri)){
358        return true; // do we always want to render properties in this case? Not sure...
359      }
360      String code = null;
361      if (uri.contains("#")) {
362        code = uri.substring(uri.indexOf("#")+1);
363        uri = uri.substring(0, uri.indexOf("#"));
364      }
365      if (Utilities.existsInList(uri, "http://hl7.org/fhir/concept-properties") || context.getCodeSystemPropList().contains(uri)) {
366        return true;
367      };
368      CodeSystem cs = getContext().getWorker().fetchCodeSystem(uri);
369      if (cs == null) {
370        return false;
371      }
372      return code == null ? false : CodeSystemUtilities.hasCode(cs, code);
373    }
374    return false;
375  }
376
377
378  private int countConcepts(List<ConceptDefinitionComponent> list) {
379    int count = list.size();
380    for (ConceptDefinitionComponent c : list)
381      if (c.hasConcept())
382        count = count + countConcepts(c.getConcept());
383    return count;
384  }
385  
386  private boolean conceptsHaveComments(ConceptDefinitionComponent c) {
387    if (ToolingExtensions.hasCSComment(c))
388      return true;
389    for (ConceptDefinitionComponent g : c.getConcept())
390      if (conceptsHaveComments(g))
391        return true;
392    return false;
393  }
394
395  private boolean conceptsHaveDisplay(ConceptDefinitionComponent c) {
396    if (c.hasDisplay() && !c.getDisplay().equals(c.getCode()))
397      return true;
398    for (ConceptDefinitionComponent g : c.getConcept())
399      if (conceptsHaveDisplay(g))
400        return true;
401    return false;
402  }
403
404  private boolean conceptsHaveVersion(ConceptDefinitionComponent c) {
405    if (c.hasUserData("cs.version.notes"))
406      return true;
407    for (ConceptDefinitionComponent g : c.getConcept())
408      if (conceptsHaveVersion(g))
409        return true;
410    return false;
411  }
412
413  private boolean conceptsHaveDeprecated(CodeSystem cs, ConceptDefinitionComponent c, boolean ignoreStatus) {
414    if (CodeSystemUtilities.isDeprecated(cs, c, ignoreStatus))
415      return true;
416    for (ConceptDefinitionComponent g : c.getConcept())
417      if (conceptsHaveDeprecated(cs, g, ignoreStatus))
418        return true;
419    return false;
420  }
421
422
423
424  private boolean addDefineRowToTable(XhtmlNode t, ConceptDefinitionComponent c, int level, boolean hasHierarchy, boolean hasDisplay, boolean hasDefinitions, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, List<PropertyComponent> properties, CodeSystemNavigator csNav, List<String> langs, boolean isSupplement) throws FHIRFormatError, DefinitionException, IOException {
425    boolean hasExtensions = false;
426    XhtmlNode tr = t.tr();
427    boolean notCurrent = CodeSystemUtilities.isNotCurrent(cs, c);
428    if (notCurrent) {
429      tr.setAttribute("style", "background-color: #ffeeee");
430    }
431    
432    XhtmlNode td = renderStatusRow(c, t, tr);
433    if (hasHierarchy) {
434      td.addText(Integer.toString(level+1));
435      td = tr.td();
436      String s = Utilities.padLeft("", '\u00A0', level*2);
437      td.addText(s);
438    }
439    String link = isSupplement ? getLinkForCode(cs.getSupplements(), null, c.getCode()) : null;
440    if (link != null) {
441      td.ah(link).style( "white-space:nowrap").addText(c.getCode());
442    } else {
443      td.style("white-space:nowrap").addText(c.getCode());
444    }      
445    XhtmlNode a;
446    if (c.hasCodeElement()) {
447      td.an(cs.getId()+"-" + Utilities.nmtokenize(c.getCode()));
448    }
449
450    if (hasDisplay) {
451      td = tr.td();
452      hasExtensions = renderDisplayName(c, cs, td, langs) || hasExtensions;
453    } 
454    if (hasDefinitions) {
455      td = tr.td();
456      if (c != null &&c.hasDefinitionElement()) {
457        // translations of the definition might come from either the translation extension, or from the designations
458        StringType defn = context.getTranslatedElement(c.getDefinitionElement());
459        boolean sl = false;
460        for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 
461          if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) { 
462            sl = true;
463          }
464        }
465
466        if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE || !(sl || ToolingExtensions.hasLanguageTranslations(defn))) {
467          if (hasMarkdownInDefinitions(cs)) {
468            addMarkdown(renderStatusDiv(defn, td), defn.asStringValue());
469          } else {
470            renderStatus(defn, td).addText(defn.asStringValue());
471          }
472        } else {
473          List<Translateable> list = new ArrayList<>();
474          list.add(new Translateable(cs.getLanguage(), defn));
475          for (Extension ext : defn.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) {
476            hasExtensions = true;
477            list.add(new Translateable(ext.getExtensionString("lang"), ext.getExtensionByUrl("content").getValueStringType()));
478          }
479          for (ConceptDefinitionDesignationComponent cd : c.getDesignation())  {
480            if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) {
481              list.add(new Translateable(cd.getLanguage(), cd.getValueElement()));
482            }
483          }
484          boolean first = true;
485          for (Translateable ti : list) {
486            if (first) {
487              first = false;
488            } else {
489              td.br();
490            }
491
492            if (ti.lang != null) {
493              td.addText(ti.lang + ": ");
494            }
495            if (hasMarkdownInDefinitions(cs)) {
496              addMarkdown(renderStatusDiv(ti.getValue(), td), ti.getValue().asStringValue());
497            } else {
498              renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue());
499            }
500          }
501        }
502      }
503    }
504    if (deprecated) {
505      td = tr.td();
506      Boolean b = CodeSystemUtilities.isDeprecated(cs, c, false);
507      if (b !=  null && b) {
508        smartAddText(td, formatPhrase(RenderingContext.CODESYSTEM_DEPRECATED));
509        hasExtensions = true;
510        if (ToolingExtensions.hasExtension(c, ToolingExtensions.EXT_REPLACED_BY)) {
511          Coding cc = (Coding) ToolingExtensions.getExtension(c, ToolingExtensions.EXT_REPLACED_BY).getValue();
512          td.tx(" "+ context.formatPhrase(RenderingContext.CODE_SYS_REPLACED_BY) + " ");
513          String url = getCodingReference(cc, system);
514          if (url != null) {
515            td.ah(url).addText(cc.getCode());
516            td.tx(": "+cc.getDisplay()+")");
517          } else
518            td.addText(cc.getCode()+" '"+cc.getDisplay()+"' in "+cc.getSystem()+")");
519        } else {
520          Extension ext = c.getExtensionByUrl(ToolingExtensions.EXT_STANDARDS_STATUS);
521          if (ext != null) {
522            ext = ext.getValue().getExtensionByUrl(ToolingExtensions.EXT_STANDARDS_STATUS_REASON);
523            if (ext != null) {
524              addMarkdown(td, ext.getValue().primitiveValue());
525            }
526          }
527        }
528      }
529    }
530    if (comment) {
531      td = tr.td();
532      Extension ext = c.getExtensionByUrl(ToolingExtensions.EXT_CS_COMMENT);
533      if (ext != null &&  ext.hasValue() && ext.getValue().primitiveValue() != null) {
534        hasExtensions = true;
535        StringType defn = context.getTranslatedElement((PrimitiveType<?>) ext.getValue());
536        if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE ||!(ToolingExtensions.hasLanguageTranslations(ext.getValue()))) {
537          td.addText(defn.asStringValue());
538        } else {
539          List<Translateable> list = new ArrayList<>();
540          list.add(new Translateable(cs.getLanguage(), defn));
541          for (Extension ex : defn.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) {
542            hasExtensions = true;
543            list.add(new Translateable(ex.getExtensionString("lang"), ex.getExtensionByUrl("content").getValueStringType()));
544          }
545          boolean first = true;
546          for (Translateable ti : list) {
547            if (first) {
548              first = false;
549            } else {
550              td.br();
551            }
552
553            if (ti.lang != null) {
554              td.addText(ti.lang + ": ");
555            }
556            renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue());
557          }
558        }
559      }      
560    }
561    if (version) {
562      td = tr.td();
563      if (c.hasUserData("cs.version.notes"))
564        td.addText(c.getUserString("cs.version.notes"));
565    }
566    if (properties != null) {
567      for (PropertyComponent pc : properties) {
568        td = tr.td();
569        boolean first = true;
570        List<ConceptPropertyComponent> pcvl = CodeSystemUtilities.getPropertyValues(c, pc.getCode());
571        for (ConceptPropertyComponent pcv : pcvl) {
572          if (pcv.hasValue()) {
573            if (first) first = false; else td.addText(", ");
574            if (pcv.hasValueCoding()) { 
575              td.addText(pcv.getValueCoding().getCode());
576            } else if (pcv.hasValueStringType() && Utilities.isAbsoluteUrl(pcv.getValue().primitiveValue())) {
577              CanonicalResource cr = (CanonicalResource) context.getContext().fetchResource(Resource.class, pcv.getValue().primitiveValue());
578              if (cr != null) {
579                td.ah(cr.getWebPath(), cr.getVersionedUrl()).tx(cr.present());
580              } else if (Utilities.isAbsoluteUrlLinkable(pcv.getValue().primitiveValue())) {
581                td.ah(pcv.getValue().primitiveValue()).tx(pcv.getValue().primitiveValue());
582              } else {
583                td.code(pcv.getValue().primitiveValue());                
584              }
585            } else if ("parent".equals(pcv.getCode())) {              
586              td.ah("#"+cs.getId()+"-"+Utilities.nmtokenize(pcv.getValue().primitiveValue())).addText(pcv.getValue().primitiveValue());
587            } else {
588              td.addText(pcv.getValue().primitiveValue());
589            }
590          }
591        }
592      }
593    }
594    
595    if (langs != null) {
596      for (String lang : langs) {
597        td = tr.td().tx(getDisplay(lang, c));
598      }
599    }
600    for (UsedConceptMap m : maps) {
601      td = tr.td();
602      List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap());
603      boolean first = true;
604      for (TargetElementComponentWrapper mapping : mappings) {
605        if (!first)
606            td.br();
607        first = false;
608        XhtmlNode span = td.span(null, mapping.comp.hasRelationship() ?  mapping.comp.getRelationship().toCode() : "");
609        span.addText(getCharForRelationship(mapping.comp));
610        a = td.ah(getContext().getLink(KnownLinkType.SPEC)+m.getLink()+"#"+makeAnchor(mapping.group.getTarget(), mapping.comp.getCode()));
611        a.addText(mapping.comp.getCode());
612        if (!Utilities.noString(mapping.comp.getComment()))
613          td.i().tx("("+mapping.comp.getComment()+")");
614      }
615    }
616    List<ConceptDefinitionComponent> ocl = csNav.getOtherChildren(c);
617    for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) {
618      hasExtensions = addDefineRowToTable(t, cc, level+1, hasHierarchy, hasDisplay, hasDefinitions, comment, version, deprecated, maps, system, cs, properties, csNav, langs, isSupplement) || hasExtensions;
619    }
620    for (ConceptDefinitionComponent cc : ocl) {
621      tr = t.tr();
622      td = tr.td();
623      td.addText(Integer.toString(level+2));
624      td = tr.td();
625      String s = Utilities.padLeft("", '\u00A0', (level+1)*2);
626      td.addText(s);
627      td.style("white-space:nowrap");
628      a = td.ah("#"+cs.getId()+"-" + Utilities.nmtokenize(cc.getCode()));
629      a.addText(cc.getCode());
630      if (hasDisplay) {
631        td = tr.td();
632        hasExtensions = renderDisplayName(cc, cs, td, langs) || hasExtensions;
633      }
634      int w = 1 + (deprecated ? 1 : 0) + (comment ? 1 : 0) + (version ? 1 : 0) + maps.size();
635      if (properties != null) {
636        w = w + properties.size();
637      }
638      td = tr.td().colspan(Integer.toString(w));
639    }
640    if (context.isCopyButton()) {
641      td = tr.td();
642      clipboard(td, "icon_clipboard_x.png", "XML", "<system value=\""+Utilities.escapeXml(cs.getUrl())+"\">\n"+(cs.getVersionNeeded() ? "<version value=\""+Utilities.escapeXml(cs.getVersion())+"\">\n" : "")+"<code value=\""+Utilities.escapeXml(c.getCode())+"\">\n<display value=\""+Utilities.escapeXml(c.getDisplay())+"\">\n");
643      td.nbsp();
644      clipboard(td, "icon_clipboard_j.png", "JSON", "\"system\" : \""+Utilities.escapeXml(cs.getUrl())+"\",\n"+(cs.getVersionNeeded() ? "\"version\" : \""+Utilities.escapeXml(cs.getVersion())+"\",\n" : "")+"\"code\" : \""+Utilities.escapeXml(c.getCode())+"\",\n\"display\" : \""+Utilities.escapeXml(c.getDisplay())+"\"\n");
645    }
646    return hasExtensions;
647  }
648
649  private String getDisplay(String lang, ConceptDefinitionComponent c) {
650    for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
651      if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(lang)) {
652        return cd.getValue();
653      }
654    }
655    for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
656      if (cd.hasLanguage() && cd.getLanguage().equals(lang)) {
657        return cd.getValue();
658      }
659    }
660    return null;
661  }
662
663  private boolean hasMarkdownInDefinitions(CodeSystem cs) {
664    if (doMarkdown == null) {
665      if (cs.hasExtension("http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown")) {
666        doMarkdown  = ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown");
667      } else {
668        doMarkdown = CodeSystemUtilities.hasMarkdownInDefinitions(cs, context.getMarkdown());
669      }
670    }
671    return doMarkdown;
672  }
673
674
675  public boolean renderDisplayName(ConceptDefinitionComponent c, CodeSystem cs, XhtmlNode td, List<String> langs) {
676    boolean hasExtensions = false;
677    if (c.hasDisplayElement()) {
678      StringType disp = c.getDisplayElement();
679      List<Translateable> list = new ArrayList<>();
680      list.add(new Translateable(cs.getLanguage(), disp));
681      for (Extension ext : disp.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) {
682        if (!langs.contains(ext.getExtensionString("lang"))) {
683          hasExtensions = true;
684          list.add(new Translateable(ext.getExtensionString("lang"), ext.getExtensionByUrl("content").getValueStringType()));
685        }
686      }
687      for (ConceptDefinitionDesignationComponent cd : c.getDesignation())  {
688        if (cd.hasLanguage() && (langs == null || !langs.contains(cd.getLanguage())) && (c.getDefinition() == null || !c.getDefinition().equalsIgnoreCase(cd.getValue()))) {
689          list.add(new Translateable(cd.getLanguage(), cd.getValueElement()));
690        }
691      }
692
693      if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE || list.size() <= 1) {
694        renderStatus(disp, td).addText(disp.asStringValue());
695      } else {
696        boolean first = true;
697        for (Translateable ti : list) {
698          if (first) {
699            first = false;
700          } else {
701            td.br();
702          }
703
704          if (ti.lang != null) {
705            td.addText(ti.lang + ": ");
706          }
707          renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue());
708        }      
709
710      }
711    }
712    return hasExtensions;
713  }
714
715  private String getCodingReference(Coding cc, String system) {
716    if (cc.getSystem().equals(system))
717      return "#"+cc.getCode();
718    if (cc.getSystem().equals("http://snomed.info/sct"))
719      return "http://snomed.info/sct/"+cc.getCode();
720    if (cc.getSystem().equals("http://loinc.org"))
721      return LoincLinker.getLinkForCode(cc.getCode());
722    return null;
723  }
724
725
726  private void addLanguageRow(ConceptDefinitionComponent c, XhtmlNode t, List<String> langs) {
727    XhtmlNode tr = t.tr();
728    tr.td().addText(c.getCode());
729    for (String lang : langs) {
730      ConceptDefinitionDesignationComponent d = null;
731      for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) {
732        if (designation.hasLanguage()) {
733          if (lang.equals(designation.getLanguage()))
734            d = designation;
735        }
736      }
737      tr.td().addText(d == null ? "" : d.getValue());
738    }
739  }
740 
741}