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