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