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