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