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    for (PropertyComponent cp : cs.getProperty()) {
259      if (showPropertyInTable(cp)) {
260        boolean exists = false;
261        for (ConceptDefinitionComponent c : cs.getConcept()) {
262          exists = exists || conceptsHaveProperty(c, cp);
263        }
264        if (exists) {
265          properties.add(cp);
266          if ("status".equals(cp.getCode())) {
267            ignoreStatus = true;
268          }
269        }
270      }
271    }
272    List<String> langs = new ArrayList<>();
273    for (ConceptDefinitionComponent c : cs.getConcept()) {
274      commentS = commentS || conceptsHaveComments(c);
275      deprecated = deprecated || conceptsHaveDeprecated(cs, c, ignoreStatus);
276      display = display || conceptsHaveDisplay(c);
277      version = version || conceptsHaveVersion(c);
278      hierarchy = hierarchy || c.hasConcept();
279      definitions = definitions || conceptsHaveDefinition(c);
280      listConceptLanguages(cs, c, langs);
281    }
282    CodeSystemNavigator csNav = new CodeSystemNavigator(cs);
283    hierarchy = hierarchy || csNav.isRestructure();
284    
285    if (langs.size() < 2) {
286      addCopyColumn(addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, definitions, commentS, version, deprecated, properties, langs, null, true), maps));
287    } else {
288      addCopyColumn(addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, definitions, commentS, version, deprecated, properties, null, null, false), maps));      
289    }
290    for (ConceptDefinitionComponent c : csNav.getConcepts(null)) {
291      addDefineRowToTable(status, t, c, 0, hierarchy, display, definitions, commentS, version, deprecated, maps, cs.getUrl(), cs, properties, csNav, langs.size() < 2 ? langs : null, isSupplement);
292    }
293    if (langs.size() >= 2) {
294      Collections.sort(langs);
295      x.para().b().tx(context.formatPhrase(RenderingContext.GENERAL_ADD_LANG));
296      t = x.table("codes", false);
297      XhtmlNode tr = t.tr();
298      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE));
299      for (String lang : langs)
300        tr.td().b().addText(describeLang(lang));
301      for (ConceptDefinitionComponent c : cs.getConcept()) {
302        addLanguageRow(c, t, langs);
303      }
304    }
305  }
306
307  private void makeHierarchyParam(XhtmlNode x, CodeSystem cs, Enumeration<CodeSystemHierarchyMeaning> hm) {
308    if (hm.hasValue()) {
309      String s = hm.getValue().getDisplay();
310      renderStatus(hm, x).tx(" "+context.formatPhrase(RenderingContext.CODE_SYS_IN_A_HIERARCHY, s));
311    } else if (VersionComparisonAnnotation.hasDeleted(cs, "hierarchyMeaning")) {
312      makeHierarchyParam(x, null, (Enumeration<CodeSystemHierarchyMeaning>) VersionComparisonAnnotation.getDeleted(cs, "hierarchyMeaning").get(0));
313    } else if (CodeSystemUtilities.hasHierarchy(cs)) {
314      x.tx(" "+ (context.formatPhrase(RenderingContext.CODE_SYS_UNDEF_HIER)));
315    } else {
316      x.tx("");
317    }
318  }
319
320  private void makeCasedParam(XhtmlNode x, CodeSystem cs, BooleanType caseSensitiveElement) {
321    if (caseSensitiveElement.hasValue()) {
322      String s = caseSensitiveElement.getValue() == true? "case-sensitive" : "case-insensitive";
323      renderStatus(caseSensitiveElement, x).tx(s);
324    } else if (VersionComparisonAnnotation.hasDeleted(cs, "caseSensitive")) {
325      makeCasedParam(x, null, (BooleanType) VersionComparisonAnnotation.getDeleted(cs, "caseSensitive").get(0));
326    } else {
327      x.tx("");
328    }
329  }
330
331  private void listConceptLanguages(CodeSystem cs, ConceptDefinitionComponent c, List<String> langs) {
332    for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
333      if (cd.hasLanguage() && !langs.contains(cd.getLanguage()) && (!cs.hasLanguage() || !cs.getLanguage().equals(cd.getLanguage()))) {
334        langs.add(cd.getLanguage());
335      }
336    }
337
338    for (ConceptDefinitionComponent g : c.getConcept()) {
339      listConceptLanguages(cs, g, langs);
340    }
341  }
342
343  private void addCopyColumn(XhtmlNode tr) {
344    if (context.isCopyButton()) {
345      tr.td().b().tx(context.formatPhrase(RenderingContext.CODE_SYS_COPY));
346    }
347    
348  }
349
350  private boolean conceptsHaveDefinition(ConceptDefinitionComponent c) {
351    if (c.hasDefinition()) {
352      return true;
353    }
354    for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 
355      if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) {
356        return true;
357      }
358    }
359    for (ConceptDefinitionComponent g : c.getConcept()) {
360      if (conceptsHaveDefinition(g)) {
361        return true;
362      }
363    }
364    return false;
365  }
366
367  private boolean conceptsHaveProperty(ConceptDefinitionComponent c, PropertyComponent cp) {
368    if (CodeSystemUtilities.hasProperty(c, cp.getCode()))
369      return true;
370    for (ConceptDefinitionComponent g : c.getConcept())
371      if (conceptsHaveProperty(g,  cp))
372        return true;
373    return false;
374
375  }
376
377  private boolean showPropertyInTable(PropertyComponent cp) {
378    return cp.hasCode();
379//      if (cp.hasExtension(ExtensionDefinitions.EXT_RENDERED_VALUE)) {
380//        return true;
381//      }
382//      if (cp.getCodeElement().hasExtension(ExtensionDefinitions.EXT_RENDERED_VALUE)) {
383//        return true;
384//      }
385//      String uri = cp.getUri();
386//      if (Utilities.noString(uri)){
387//        return true; // do we always want to render properties in this case? Not sure...
388//      }
389//      String code = null;
390//      if (uri.contains("#")) {
391//        code = uri.substring(uri.indexOf("#")+1);
392//        uri = uri.substring(0, uri.indexOf("#"));
393//      }
394//      if (Utilities.existsInList(uri, "http://hl7.org/fhir/concept-properties") || context.getCodeSystemPropList().contains(uri)) {
395//        return true;
396//      };
397//      CodeSystem cs = getContext().getWorker().fetchCodeSystem(uri);
398//      if (cs == null) {
399//        return false;
400//      }
401//      switch ()
402//      return code == null ? false : CodeSystemUtilities.hasCode(cs, code);
403  }
404
405
406  private int countConcepts(List<ConceptDefinitionComponent> list) {
407    int count = list.size();
408    for (ConceptDefinitionComponent c : list)
409      if (c.hasConcept())
410        count = count + countConcepts(c.getConcept());
411    return count;
412  }
413  
414  private boolean conceptsHaveComments(ConceptDefinitionComponent c) {
415    if (ExtensionUtilities.hasCSComment(c))
416      return true;
417    for (ConceptDefinitionComponent g : c.getConcept())
418      if (conceptsHaveComments(g))
419        return true;
420    return false;
421  }
422
423  private boolean conceptsHaveDisplay(ConceptDefinitionComponent c) {
424    if (c.hasDisplay() && !c.getDisplay().equals(c.getCode()))
425      return true;
426    for (ConceptDefinitionComponent g : c.getConcept())
427      if (conceptsHaveDisplay(g))
428        return true;
429    return false;
430  }
431
432  private boolean conceptsHaveVersion(ConceptDefinitionComponent c) {
433    if (c.hasUserData(UserDataNames.tx_cs_version_notes))
434      return true;
435    for (ConceptDefinitionComponent g : c.getConcept())
436      if (conceptsHaveVersion(g))
437        return true;
438    return false;
439  }
440
441  private boolean conceptsHaveDeprecated(CodeSystem cs, ConceptDefinitionComponent c, boolean ignoreStatus) {
442    if (CodeSystemUtilities.isDeprecated(cs, c, ignoreStatus))
443      return true;
444    for (ConceptDefinitionComponent g : c.getConcept())
445      if (conceptsHaveDeprecated(cs, g, ignoreStatus))
446        return true;
447    return false;
448  }
449
450
451
452  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 {
453    boolean hasExtensions = false;
454    XhtmlNode tr = t.tr();
455    boolean notCurrent = CodeSystemUtilities.isNotCurrent(cs, c);
456    if (notCurrent) {
457      tr.setAttribute("style", "background-color: #ffeeee");
458    }
459    
460    XhtmlNode td = renderStatusRow(c, t, tr);
461    if (hasHierarchy) {
462      td.addText(Integer.toString(level+1));
463      td = tr.td();
464      String s = Utilities.padLeft("", '\u00A0', level*2);
465      td.addText(s);
466    }
467    String link = isSupplement ? getLinkForCode(cs.getSupplements(), null, c.getCode()) : null;
468    if (link != null) {
469      td.ah(context.prefixLocalHref(link)).style( "white-space:nowrap").addText(c.getCode());
470    } else {
471      td.style("white-space:nowrap").addText(c.getCode());
472    }      
473    XhtmlNode a;
474    if (c.hasCodeElement()) {
475      td.an(context.prefixAnchor(cs.getId()+"-" + Utilities.nmtokenize(c.getCode())));
476    }
477
478    if (hasDisplay) {
479      td = tr.td();
480      hasExtensions = renderDisplayName(c, cs, td, langs) || hasExtensions;
481    } 
482    if (hasDefinitions) {
483      td = tr.td();
484      if (c != null &&c.hasDefinitionElement()) {
485        // translations of the definition might come from either the translation extension, or from the designations
486        StringType defn = context.getTranslatedElement(c.getDefinitionElement());
487        boolean sl = false;
488        for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 
489          if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) { 
490            sl = true;
491          }
492        }
493
494        if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE || !(sl || ExtensionUtilities.hasLanguageTranslations(defn))) {
495          if (hasMarkdownInDefinitions(cs)) {
496            addMarkdown(renderStatusDiv(defn, td), defn.asStringValue());
497          } else {
498            renderStatus(defn, td).addText(defn.asStringValue());
499          }
500        } else {
501          List<Translateable> list = new ArrayList<>();
502          list.add(new Translateable(cs.getLanguage(), defn));
503          for (Extension ext : defn.getExtensionsByUrl(ExtensionDefinitions.EXT_TRANSLATION)) {
504            hasExtensions = true;
505            list.add(new Translateable(ext.getExtensionString("lang"), ext.getExtensionByUrl("content").getValueStringType()));
506          }
507          for (ConceptDefinitionDesignationComponent cd : c.getDesignation())  {
508            if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) {
509              list.add(new Translateable(cd.getLanguage(), cd.getValueElement()));
510            }
511          }
512          boolean first = true;
513          for (Translateable ti : list) {
514            if (first) {
515              first = false;
516            } else {
517              td.br();
518            }
519
520            if (ti.lang != null) {
521              td.addText(ti.lang + ": ");
522            }
523            if (hasMarkdownInDefinitions(cs)) {
524              addMarkdown(renderStatusDiv(ti.getValue(), td), ti.getValue().asStringValue());
525            } else {
526              renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue());
527            }
528          }
529        }
530      }
531    }
532    if (deprecated) {
533      td = tr.td();
534      Boolean b = CodeSystemUtilities.isDeprecated(cs, c, false);
535      if (b !=  null && b) {
536        td.addTextWithLineBreaks(formatPhrase(RenderingContext.CODESYSTEM_DEPRECATED));
537        hasExtensions = true;
538        if (ExtensionUtilities.hasExtension(c, ExtensionDefinitions.EXT_REPLACED_BY)) {
539          Coding cc = (Coding) ExtensionUtilities.getExtension(c, ExtensionDefinitions.EXT_REPLACED_BY).getValue();
540          td.tx(" "+ context.formatPhrase(RenderingContext.CODE_SYS_REPLACED_BY) + " ");
541          String url = getCodingReference(cc, system);
542          if (url != null) {
543            td.ah(context.prefixLocalHref(url)).addText(cc.getCode());
544            td.tx(": "+cc.getDisplay()+")");
545          } else
546            td.addText(cc.getCode()+" '"+cc.getDisplay()+"' in "+cc.getSystem()+")");
547        } else {
548          Extension ext = c.getExtensionByUrl(ExtensionDefinitions.EXT_STANDARDS_STATUS);
549          if (ext != null) {
550            ext = ext.getValue().getExtensionByUrl(ExtensionDefinitions.EXT_STANDARDS_STATUS_REASON);
551            if (ext != null) {
552              addMarkdown(td, ext.getValue().primitiveValue());
553            }
554          }
555        }
556      }
557    }
558    if (comment) {
559      td = tr.td();
560      Extension ext = c.getExtensionByUrl(ExtensionDefinitions.EXT_CS_COMMENT);
561      if (ext != null &&  ext.hasValue() && ext.getValue().primitiveValue() != null) {
562        hasExtensions = true;
563        StringType defn = context.getTranslatedElement((PrimitiveType<?>) ext.getValue());
564        if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE ||!(ExtensionUtilities.hasLanguageTranslations(ext.getValue()))) {
565          td.addText(defn.asStringValue());
566        } else {
567          List<Translateable> list = new ArrayList<>();
568          list.add(new Translateable(cs.getLanguage(), defn));
569          for (Extension ex : defn.getExtensionsByUrl(ExtensionDefinitions.EXT_TRANSLATION)) {
570            hasExtensions = true;
571            list.add(new Translateable(ex.getExtensionString("lang"), ex.getExtensionByUrl("content").getValueStringType()));
572          }
573          boolean first = true;
574          for (Translateable ti : list) {
575            if (first) {
576              first = false;
577            } else {
578              td.br();
579            }
580
581            if (ti.lang != null) {
582              td.addText(ti.lang + ": ");
583            }
584            renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue());
585          }
586        }
587      }      
588    }
589    if (version) {
590      td = tr.td();
591      if (c.hasUserData(UserDataNames.tx_cs_version_notes)) { // todo: this is never set
592        td.addText(c.getUserString(UserDataNames.tx_cs_version_notes));
593      }
594    }
595
596    if (properties != null) {
597      for (PropertyComponent pc : properties) {
598        td = tr.td();
599        boolean first = true;
600        List<ConceptPropertyComponent> pcvl = CodeSystemUtilities.getPropertyValues(c, pc.getCode());
601        for (ConceptPropertyComponent pcv : pcvl) {
602          if (pcv.hasValue()) {
603            if (first) first = false; else td.addText(", ");
604            if (pcv.hasValueCoding()) { 
605              td.addText(pcv.getValueCoding().getCode());
606            } else {
607              String pv = pcv.getValue().primitiveValue();
608              if (pcv.hasValueStringType() && Utilities.isAbsoluteUrl(pv)) {
609                CanonicalResource cr = (CanonicalResource) context.getContext().fetchResource(Resource.class, pv);
610                if (cr != null) {
611                  if (cr.hasWebPath()) {
612                    td.ah(context.prefixLocalHref(cr.getWebPath()), cr.getVersionedUrl()).tx(cr.present());
613                  } else {
614                    td.ah(cr.getVersionedUrl(), cr.getVersionedUrl()).tx(cr.present());
615                  }
616                } else if (Utilities.isAbsoluteUrlLinkable(pv) && !isInKnownUrlSpace(pv)) {
617                  td.ah(context.prefixLocalHref(pv)).tx(pv);
618                } else {
619                  td.code(pv);                
620                }
621              } else if ("parent".equals(pcv.getCode())) {              
622                td.ah(context.prefixLocalHref("#"+cs.getId()+"-"+Utilities.nmtokenize(pv))).addText(pv);
623              } else {
624                td.addText(pv);
625              }
626            }
627          }
628        }
629      }
630    }
631    
632    if (langs != null) {
633      for (String lang : langs) {
634        td = tr.td().tx(getDisplay(lang, c));
635      }
636    }
637    for (UsedConceptMap m : maps) {
638      td = tr.td();
639      List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap());
640      boolean first = true;
641      for (TargetElementComponentWrapper mapping : mappings) {
642        if (!first)
643            td.br();
644        first = false;
645        XhtmlNode span = td.span(null, mapping.comp.hasRelationship() ?  mapping.comp.getRelationship().toCode() : "");
646        span.addText(getCharForRelationship(mapping.comp));
647        a = td.ah(context.prefixLocalHref(getContext().getLink(KnownLinkType.SPEC, true)+m.getLink()+"#"+makeAnchor(mapping.group.getTarget(), mapping.comp.getCode())));
648        a.addText(mapping.comp.getCode());
649        if (!Utilities.noString(mapping.comp.getComment()))
650          td.i().tx("("+mapping.comp.getComment()+")");
651      }
652    }
653    List<ConceptDefinitionComponent> ocl = csNav.getOtherChildren(c);
654    for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) {
655       addDefineRowToTable(status, t, cc, level+1, hasHierarchy, hasDisplay, hasDefinitions, comment, version, deprecated, maps, system, cs, properties, csNav, langs, isSupplement);
656    }
657    for (ConceptDefinitionComponent cc : ocl) {
658      tr = t.tr();
659      td = tr.td();
660      td.addText(Integer.toString(level+2));
661      td = tr.td();
662      String s = Utilities.padLeft("", '\u00A0', (level+1)*2);
663      td.addText(s);
664      td.style("white-space:nowrap");
665      a = td.ah(context.prefixLocalHref("#"+cs.getId()+"-" + Utilities.nmtokenize(cc.getCode())));
666      a.addText(cc.getCode());
667      if (hasDisplay) {
668        td = tr.td();
669        hasExtensions = renderDisplayName(cc, cs, td, langs) || hasExtensions;
670      }
671      int w = 1 + (deprecated ? 1 : 0) + (comment ? 1 : 0) + (version ? 1 : 0) + maps.size();
672      if (properties != null) {
673        w = w + properties.size();
674      }
675      td = tr.td().colspan(Integer.toString(w));
676    }
677    if (context.isCopyButton()) {
678      td = tr.td();
679      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");
680      td.nbsp();
681      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");
682    }
683  }
684
685
686  private String getDisplay(String lang, ConceptDefinitionComponent c) {
687    for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
688      if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(lang)) {
689        return cd.getValue();
690      }
691    }
692    for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
693      if (cd.hasLanguage() && cd.getLanguage().equals(lang)) {
694        return cd.getValue();
695      }
696    }
697    return null;
698  }
699
700  private boolean hasMarkdownInDefinitions(CodeSystem cs) {
701    if (doMarkdown == null) {
702      if (cs.hasUserData(UserDataNames.CS_MARKDOWN_FLAG)) {
703        doMarkdown = (Boolean) cs.getUserData(UserDataNames.CS_MARKDOWN_FLAG);
704      } else {
705      if (cs.hasExtension("http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown")) {
706        doMarkdown  = ExtensionUtilities.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown");
707      } else {
708        doMarkdown = CodeSystemUtilities.hasMarkdownInDefinitions(cs, context.getMarkdown());
709      }
710        cs.setUserData(UserDataNames.CS_MARKDOWN_FLAG, doMarkdown);
711      }
712    }
713    return doMarkdown;
714  }
715
716
717  public boolean renderDisplayName(ConceptDefinitionComponent c, CodeSystem cs, XhtmlNode td, List<String> langs) {
718    boolean hasExtensions = false;
719    if (c.hasDisplayElement()) {
720      StringType disp = c.getDisplayElement();
721      List<Translateable> list = new ArrayList<>();
722      list.add(new Translateable(cs.getLanguage(), disp));
723      for (Extension ext : disp.getExtensionsByUrl(ExtensionDefinitions.EXT_TRANSLATION)) {
724        if (!langs.contains(ext.getExtensionString("lang"))) {
725          hasExtensions = true;
726          list.add(new Translateable(ext.getExtensionString("lang"), ext.getExtensionByUrl("content").getValueStringType()));
727        }
728      }
729      for (ConceptDefinitionDesignationComponent cd : c.getDesignation())  {
730        if (cd.hasLanguage() && (langs == null || !langs.contains(cd.getLanguage())) && (c.getDefinition() == null || !c.getDefinition().equalsIgnoreCase(cd.getValue()))) {
731          list.add(new Translateable(cd.getLanguage(), cd.getValueElement()));
732        }
733      }
734
735      if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE || list.size() <= 1) {
736        renderStatus(disp, td).addText(disp.asStringValue());
737      } else {
738        boolean first = true;
739        for (Translateable ti : list) {
740          if (first) {
741            first = false;
742          } else {
743            td.br();
744          }
745
746          if (ti.lang != null) {
747            td.addText(ti.lang + ": ");
748          }
749          renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue());
750        }      
751
752      }
753    }
754    return hasExtensions;
755  }
756
757  private String getCodingReference(Coding cc, String system) {
758    if (cc.getSystem().equals(system))
759      return "#"+cc.getCode();
760    if (cc.getSystem().equals("http://snomed.info/sct"))
761      return "http://snomed.info/sct/"+cc.getCode();
762    if (cc.getSystem().equals("http://loinc.org"))
763      return LoincLinker.getLinkForCode(cc.getCode());
764    return null;
765  }
766
767
768  private void addLanguageRow(ConceptDefinitionComponent c, XhtmlNode t, List<String> langs) {
769    XhtmlNode tr = t.tr();
770    tr.td().addText(c.getCode());
771    for (String lang : langs) {
772      ConceptDefinitionDesignationComponent d = null;
773      for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) {
774        if (designation.hasLanguage()) {
775          if (lang.equals(designation.getLanguage()))
776            d = designation;
777        }
778      }
779      tr.td().addText(d == null ? "" : d.getValue());
780    }
781  }
782 
783
784  @Override
785  protected void genSummaryTableContent(RenderingStatus status, XhtmlNode tbl, CanonicalResource cr) throws IOException {
786    super.genSummaryTableContent(status, tbl, cr);
787
788    CodeSystem cs = (CodeSystem) cr;
789    XhtmlNode tr;
790    if (cs.hasContent()) {
791      tr = tbl.tr();
792      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_CONTENT)+":");
793      XhtmlNode td = tr.td();
794      td.tx((cs.getContent().getDisplay())+": "+describeContent(cs.getContent(), cs));
795      if (cs.getContent() == CodeSystemContentMode.SUPPLEMENT) {
796        td.tx(" ");
797        CodeSystem tgt = context.getContext().fetchCodeSystem(cs.getSupplements());
798        if (tgt != null) {
799          td.ah(tgt.getWebPath()).tx(tgt.present());
800        } else {
801          td.code().tx(cs.getSupplements());
802        }            
803      }
804    }
805    
806    if (CodeSystemUtilities.hasOID(cs)) {
807      tr = tbl.tr();
808      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_OID)+":");
809      tr.td().tx(context.formatPhrase(RenderingContext.CODE_SYS_FOR_OID, CodeSystemUtilities.getOID(cs)));
810    }
811
812    if (cs.hasValueSet()) {
813      tr = tbl.tr();
814      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUESET)+":");
815      ValueSet vs = context.getContext().findTxResource(ValueSet.class, cs.getValueSet());
816      if (vs == null) {
817        tr.td().tx(context.formatPhrase(RenderingContext.CODE_SYS_THE_VALUE_SET, cs.getValueSet())+")");
818      } else {
819        tr.td().ah(vs.getWebPath()).tx(context.formatPhrase(RenderingContext.CODE_SYS_THE_VALUE_SET, cs.getValueSet())+")");
820      }
821    }
822  }
823
824  private String describeContent(CodeSystemContentMode content, CodeSystem cs) {
825    switch (content) {
826    case COMPLETE: return (context.formatPhrase(RenderingContext.CODE_SYS_COMPLETE));
827    case NOTPRESENT: return (context.formatPhrase(RenderingContext.CODE_SYS_NOTPRESENT));
828    case EXAMPLE: return (context.formatPhrase(RenderingContext.CODE_SYS_EXAMPLE));
829    case FRAGMENT: return (context.formatPhrase(RenderingContext.CODE_SYS_FRAGMENT));
830    case SUPPLEMENT: return (context.formatPhrase(RenderingContext.CODE_SYS_SUPPLEMENT));
831    default:
832      return "?? illegal content status value "+(content == null ? "(null)" : content.toCode());
833    }
834  }
835
836
837}