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