001package org.hl7.fhir.r5.renderers;
002
003import java.io.IOException;
004import java.io.UnsupportedEncodingException;
005import java.text.ParseException;
006import java.text.SimpleDateFormat;
007import java.util.ArrayList;
008import java.util.Collections;
009import java.util.Date;
010import java.util.HashMap;
011import java.util.HashSet;
012import java.util.List;
013import java.util.Map;
014import java.util.Set;
015
016import org.hl7.fhir.exceptions.DefinitionException;
017import org.hl7.fhir.exceptions.FHIRException;
018import org.hl7.fhir.exceptions.FHIRFormatError;
019import org.hl7.fhir.exceptions.TerminologyServiceException;
020import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation;
021import org.hl7.fhir.r5.model.Base;
022import org.hl7.fhir.r5.model.CanonicalResource;
023import org.hl7.fhir.r5.model.CodeSystem;
024import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
025import org.hl7.fhir.r5.model.Coding;
026import org.hl7.fhir.r5.model.ConceptMap;
027import org.hl7.fhir.r5.model.DataType;
028import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
029import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
030import org.hl7.fhir.r5.model.Expression;
031import org.hl7.fhir.r5.model.Extension;
032import org.hl7.fhir.r5.model.ExtensionHelper;
033import org.hl7.fhir.r5.model.PrimitiveType;
034import org.hl7.fhir.r5.model.Resource;
035import org.hl7.fhir.r5.model.StringType;
036import org.hl7.fhir.r5.model.UriType;
037import org.hl7.fhir.r5.model.ValueSet;
038import org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent;
039import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
040import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent;
041import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
042import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
043import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
044import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
045import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
046import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
047import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
048import org.hl7.fhir.r5.renderers.utils.RenderingContext;
049import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
050import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
051import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
052import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
053import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
054import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest;
055import org.hl7.fhir.r5.terminologies.utilities.SnomedUtilities;
056import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
057import org.hl7.fhir.r5.utils.EOperationOutcome;
058import org.hl7.fhir.r5.utils.ToolingExtensions;
059import org.hl7.fhir.r5.utils.UserDataNames;
060import org.hl7.fhir.utilities.LoincLinker;
061import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
062import org.hl7.fhir.utilities.Utilities;
063import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
064import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
065import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
066import org.hl7.fhir.utilities.xhtml.XhtmlNode;
067
068import com.google.common.collect.HashMultimap;
069import com.google.common.collect.Multimap;
070
071@MarkedToMoveToAdjunctPackage
072public class ValueSetRenderer extends TerminologyRenderer {
073
074  public ValueSetRenderer(RenderingContext context) { 
075    super(context); 
076  }
077 
078  @Override
079  public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome {
080    if (!r.isDirect()) {
081      // the intention is to change this in the future
082      x.para().tx("ValueSetRenderer only renders native resources directly");
083    } else {
084      renderResourceTechDetails(r, x);
085      ValueSet vs = (ValueSet) r.getBase();
086      genSummaryTable(status, x, vs);
087      List<UsedConceptMap> maps = findReleventMaps(vs);
088
089      if (context.isShowSummaryTable()) {
090        XhtmlNode h = x.h2();
091        h.addText(vs.hasTitle() ? vs.getTitle() : vs.getName());
092        addMarkdown(x, vs.getDescription());
093        if (vs.hasCopyright())
094          generateCopyright(x, r);
095      }
096      if (vs.hasExtension(ToolingExtensions.EXT_VS_CS_SUPPL_NEEDED)) {
097        var p = x.para();
098        p.tx("This ValueSet requires the Code system Supplement ");
099        String u = ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_VS_CS_SUPPL_NEEDED);
100        CodeSystem cs = context.getContext().fetchResource(CodeSystem.class, u);
101        if (cs == null) {
102          p.code().tx(u);
103        } else if (!cs.hasWebPath()) {
104          p.ah(u).tx(cs.present());
105        } else {
106          p.ah(cs.getWebPath()).tx(cs.present());          
107        }
108        p.tx(".");
109      }
110      if (vs.hasExtension(ToolingExtensions.EXT_VALUESET_PARAMETER)) {
111        x.para().b().tx("This ValueSet has parameters");
112        XhtmlNode tbl = x.table("grid");
113        XhtmlNode tr = tbl.tr();
114        tr.th().tx("Name");
115        tr.th().tx("Documentation");
116        for (Extension ext : vs.getExtensionsByUrl(ToolingExtensions.EXT_VALUESET_PARAMETER)) {
117          tr = tbl.tr();
118          tr.td().tx(ext.getExtensionString("name"));
119          tr.td().markdown(ext.getExtensionString("documentation"), "parameter.documentation");    
120        }
121      }
122      if (vs.hasExpansion()) {
123        // for now, we just accept an expansion if there is one
124        generateExpansion(status, r, x, vs, false, maps);
125      } else {
126        generateComposition(status, r, x, vs, false, maps);
127      }
128    }
129  }
130
131  
132  @Override
133  public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
134    return canonicalTitle(r);
135  }
136
137  private static final int MAX_DESIGNATIONS_IN_LINE = 5;
138
139  private static final int MAX_BATCH_VALIDATION_SIZE = 1000;
140
141  private List<ConceptMapRenderInstructions> renderingMaps = new ArrayList<ConceptMapRenderInstructions>();
142  
143
144  public void render(RenderingStatus status, XhtmlNode x, ValueSet vs, boolean header) throws FHIRFormatError, DefinitionException, IOException {
145    
146  }
147
148  public void describe(XhtmlNode x, ValueSet vs) {
149    x.tx(display(vs));
150  }
151
152  public String display(ValueSet vs) {
153    return vs.present();
154  }
155
156  
157  private List<UsedConceptMap> findReleventMaps(ValueSet vs) throws FHIRException {
158    List<UsedConceptMap> res = new ArrayList<UsedConceptMap>();
159    for (ConceptMap cm : getContext().getWorker().fetchResourcesByType(ConceptMap.class)) {
160      if (isSource(vs, cm.getSourceScope())) {
161        ConceptMapRenderInstructions re = findByTarget(cm.getTargetScope());
162        if (re == null) {
163          re = new ConceptMapRenderInstructions(cm.present(), cm.getUrl(), false);
164        }
165        if (re != null) {
166          ValueSet vst = cm.hasTargetScope() ? getContext().getWorker().findTxResource(ValueSet.class, cm.hasTargetScopeCanonicalType() ? cm.getTargetScopeCanonicalType().getValue() : cm.getTargetScopeUriType().asStringValue(), cm) : null;
167          res.add(new UsedConceptMap(re, vst == null ? cm.getWebPath() : vst.getWebPath(), cm));
168        }
169      }
170    }
171    return res;
172
173//    @Override
174//    public List<ConceptMap> findMapsForSource(String url) throws FHIRException {
175//      synchronized (lock) {
176//        List<ConceptMap> res = new ArrayList<ConceptMap>();
177//        for (ConceptMap map : maps.getList()) {
178//          if (((Reference) map.getSourceScope()).getReference().equals(url)) { 
179//            res.add(map);
180//          } 
181//        } 
182//        return res;
183//      }
184//    }
185
186//    Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>();
187//  for (ConceptMap a : context.getWorker().findMapsForSource(vs.getUrl())) {
188//    String url = "";
189//    ValueSet vsr = context.getWorker().findTxResource(ValueSet.class, ((Reference) a.getTarget()).getReference());
190//    if (vsr != null)
191//      url = (String) vsr.getUserData(UserDataNames.filename);
192//    mymaps.put(a, url);
193//  }
194//    Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>();
195//  for (ConceptMap a : context.getWorker().findMapsForSource(cs.getValueSet())) {
196//    String url = "";
197//    ValueSet vsr = context.getWorker().fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference());
198//    if (vsr != null)
199//      url = (String) vsr.getUserData(UserDataNames.filename);
200//    mymaps.put(a, url);
201//  }
202    // also, look in the contained resources for a concept map
203//    for (Resource r : cs.getContained()) {
204//      if (r instanceof ConceptMap) {
205//        ConceptMap cm = (ConceptMap) r;
206//        if (((Reference) cm.getSource()).getReference().equals(cs.getValueSet())) {
207//          String url = "";
208//          ValueSet vsr = context.getWorker().findTxResource(ValueSet.class, ((Reference) cm.getTarget()).getReference());
209//          if (vsr != null)
210//              url = (String) vsr.getUserData(UserDataNames.filename);
211//        mymaps.put(cm, url);
212//        }
213//      }
214//    }
215  }  
216  
217  private boolean isSource(ValueSet vs, DataType source) {
218    return vs.hasUrl() && source != null && vs.getUrl().equals(source.primitiveValue());
219  }  
220  
221  private void generateExpansion(RenderingStatus status, ResourceWrapper res, XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException {
222    List<String> langs = new ArrayList<String>();
223    Map<String, String> designations = new HashMap<>(); //  map of url = description, where url is the designation code. Designations that are for languages won't make it into this list
224    Map<String, String> properties = new HashMap<>(); //  map of url = description, where url is the designation code. Designations that are for languages won't make it into this list
225
226    if (header) {
227      XhtmlNode h = x.addTag(getHeader());
228      h.tx(context.formatPhrase(RenderingContext.VALUE_SET_CONT));
229      if (IsNotFixedExpansion(vs))
230        addMarkdown(x, vs.getDescription());
231      if (vs.hasCopyright())
232        generateCopyright(x, res);
233    }
234    boolean hasFragment = generateContentModeNotices(x, vs.getExpansion(), vs);
235    generateVersionNotice(x, vs.getExpansion(), vs);
236    
237    if (ToolingExtensions.hasExtension(vs.getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY)) {
238      String msg = null;
239      if (vs.getExpansion().getContains().isEmpty()) {
240        msg = context.formatPhrase(RenderingContext.VALUE_SET_TOO_COSTLY);
241      } else {
242        msg = context.formatPhrase(RenderingContext.VALUE_SET_CODE_SELEC, countMembership(vs));
243      }
244      x.para().style("border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(msg);
245    } else {
246      int count = ValueSetUtilities.countExpansion(vs);
247      if (vs.getExpansion().hasTotal()) {
248        if (count != vs.getExpansion().getTotal()) {
249          x.para().style("border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px")
250            .addText(context.formatPhrase(hasFragment ? RenderingContext.VALUE_SET_HAS_AT_LEAST : RenderingContext.VALUE_SET_HAS, vs.getExpansion().getTotal(), count));
251        } else {
252          x.para().tx(context.formatPhrase(hasFragment ? RenderingContext.VALUE_SET_CONTAINS_AT_LEAST : RenderingContext.VALUE_SET_CONTAINS, vs.getExpansion().getTotal()));          
253        }
254      } else if (count == 1000) {
255        // it's possible that there's exactly 1000 codes, in which case wht we're about to do is wrong
256        // work in progress to tighten up the terminology system to always return a total...
257        String msg = context.formatPhrase(RenderingContext.VALUE_SET_SEL);    
258        x.para().style("border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(msg);        
259      } else {
260        x.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_NUMBER_CONCEPTS, count));
261      }
262    }
263    
264
265    boolean doLevel = false;
266    for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
267      if (cc.hasContains()) {
268        doLevel = true;
269        break;
270      }
271    }
272    boolean doInactive = checkDoInactive(vs.getExpansion().getContains());    
273    boolean doDefinition = checkDoDefinition(vs.getExpansion().getContains());
274    
275    XhtmlNode t = x.table("codes", false);
276    XhtmlNode tr = t.tr();
277    if (doLevel)
278      tr.td().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_LEVEL));
279    tr.td().attribute("style", "white-space:nowrap").b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE));
280    tr.td().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_SYSTEM));
281    XhtmlNode tdDisp = tr.td();
282    String displang = vs.getLanguage();
283    if (displang == null) {
284      displang = findParamValue(vs.getExpansion().getParameter(), "displayLanguage");
285    }
286    if (displang == null) {
287      tdDisp.b().tx(context.formatPhrase(RenderingContext.TX_DISPLAY));
288    } else {
289      tdDisp.b().tx(context.formatPhrase(RenderingContext.TX_DISPLAY_LANG, displang));
290    }
291    boolean doDesignations = false;
292    for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
293      scanForDesignations(c, langs, designations);
294    }
295    scanForProperties(vs.getExpansion(), langs, properties);
296    if (doInactive) {
297      tr.td().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_INACTIVE));
298    }
299    if (doDefinition) {
300      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINITION));
301      doDesignations = false;
302      for (String n : Utilities.sorted(properties.keySet())) {
303        tr.td().b().ah(context.prefixLocalHref(properties.get(n))).addText(n);        
304      }
305    } else {
306      for (String n : Utilities.sorted(properties.keySet())) {
307        tr.td().b().ah(context.prefixLocalHref(properties.get(n))).addText(n);        
308      }
309      // if we're not doing definitions and we don't have too many languages, we'll do them in line
310      doDesignations = langs.size() + properties.size() + designations.size() < MAX_DESIGNATIONS_IN_LINE;
311
312      if (doDesignations) {
313        if (vs.hasLanguage()) {
314          tdDisp.tx(" - "+describeLang(vs.getLanguage()));
315        }
316        for (String url : designations.keySet()) {
317          tr.td().b().addText(designations.get(url));
318        }
319        for (String lang : langs) {
320          tr.td().b().addText(describeLang(lang));
321        }
322      }
323    }
324
325    
326    addMapHeaders(tr, maps);
327    for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
328      addExpansionRowToTable(t, vs, c, 1, doLevel, doDefinition, doInactive, maps, langs, designations, doDesignations, properties, res);
329    }
330
331    // now, build observed languages
332
333    if (!doDesignations && langs.size() + designations.size() > 0) {
334      Collections.sort(langs);
335      if (designations.size() == 0) {
336        x.para().b().tx(context.formatPhrase(RenderingContext.GENERAL_ADD_LANG));
337      } else if (langs.size() == 0) {
338        x.para().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_DESIG));
339      } else {
340        x.para().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_ADD_DESIG));
341      }
342      t = x.table("codes", false);
343      tr = t.tr();
344      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE));
345      for (String url : designations.keySet()) {
346        tr.td().b().addText(designations.get(url));
347      }
348      for (String lang : langs) {
349        tr.td().b().addText(describeLang(lang));
350      }
351      for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
352        addDesignationRow(c, t, langs, designations);
353      }
354    }
355
356  }
357
358
359  private void scanForProperties(ValueSetExpansionComponent exp, List<String> langs, Map<String, String> properties) {
360    properties.clear();
361    for (ValueSetExpansionPropertyComponent pp : exp.getProperty()) {
362      if (pp.hasCode() && pp.hasUri() && anyActualproperties(exp.getContains(), pp.getCode())) {
363        properties.put(pp.getCode(), pp.getUri());
364      }
365    }
366  }
367
368  private boolean anyActualproperties(List<ValueSetExpansionContainsComponent> contains, String pp) {
369    for (ValueSetExpansionContainsComponent c : contains) {
370      for (ConceptPropertyComponent cp : c.getProperty()) {
371        if (pp.equals(cp.getCode())) {
372          return true;
373        }
374      }
375      if (anyActualproperties(c.getContains(), pp)) {
376        return true;
377      }
378    }
379    return false;
380  }
381
382  private boolean generateContentModeNotices(XhtmlNode x, ValueSetExpansionComponent expansion, Resource vs) {
383    generateContentModeNotice(x, expansion, "example", context.formatPhrase(RenderingContext.VALUE_SET_EXP), vs); 
384    return generateContentModeNotice(x, expansion, "fragment", context.formatPhrase(RenderingContext.VALUE_SET_EXP_FRAG), vs); 
385  }
386  
387  private boolean generateContentModeNotice(XhtmlNode x, ValueSetExpansionComponent expansion, String mode, String text, Resource vs) {
388    boolean res = false;
389    Multimap<String, String> versions = HashMultimap.create();
390    for (ValueSetExpansionParameterComponent p : expansion.getParameter()) {
391      if (p.getName().equals(mode)) {
392        String[] parts = ((PrimitiveType) p.getValue()).asStringValue().split("\\|");
393        if (parts.length == 2 && !Utilities.noString(parts[0]))
394          versions.put(parts[0], parts[1]);
395      }
396    }
397    if (versions.size() > 0) {
398      XhtmlNode div = null;
399      XhtmlNode ul = null;
400      boolean first = true;
401      for (String s : versions.keySet()) {
402        if (versions.size() == 1 && versions.get(s).size() == 1) {
403          for (String v : versions.get(s)) { // though there'll only be one
404            XhtmlNode p = x.para().style("border: black 1px dotted; background-color: #ffcccc; padding: 8px; margin-bottom: 8px");
405            p.tx(text+" ");
406            expRef(p, s, v, vs);
407            res = true;
408          }
409        } else {
410          for (String v : versions.get(s)) {
411            if (first) {
412              div = x.div().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px");
413              div.para().tx(text+"s: ");
414              ul = div.ul();
415              first = false;
416              res = true;
417            }
418            expRef(ul.li(), s, v, vs);
419          }
420        }
421      }
422    }
423    return res;
424  }
425
426  private boolean checkDoSystem(ValueSet vs, ValueSet src) {
427    if (src != null)
428      vs = src;
429    return vs.hasCompose();
430  }
431
432  private boolean IsNotFixedExpansion(ValueSet vs) {
433    if (vs.hasCompose())
434      return false;
435
436
437    // it's not fixed if it has any includes that are not version fixed
438    for (ConceptSetComponent cc : vs.getCompose().getInclude()) {
439      if (cc.hasValueSet())
440        return true;
441      if (!cc.hasVersion())
442        return true;
443    }
444    return false;
445  }
446
447
448 
449  
450  private ConceptMapRenderInstructions findByTarget(DataType source) {
451    if (source == null) {
452      return null;
453    }
454    String src = source.primitiveValue();
455    if (src == null) {
456      return null;
457    }
458    for (ConceptMapRenderInstructions t : renderingMaps) {
459      if (src.equals(t.getUrl()))
460        return t;
461    }
462    return null;    
463  }
464
465  private Integer countMembership(ValueSet vs) {
466    int count = 0;
467    if (vs.hasExpansion())
468      count = count + ValueSetUtilities.countExpansion(vs);
469    else {
470      if (vs.hasCompose()) {
471        if (vs.getCompose().hasExclude()) {
472          try {
473            ValueSetExpansionOutcome vse = getContext().getWorker().expandVS(vs, true, false);
474            count = 0;
475            count += ValueSetUtilities.countExpansion(vse.getValueset());
476            return count;
477          } catch (Exception e) {
478            return null;
479          }
480        }
481        for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
482          if (inc.hasFilter())
483            return null;
484          if (!inc.hasConcept())
485            return null;
486          count = count + inc.getConcept().size();
487        }
488      }
489    }
490    return count;
491  }
492
493
494  private void addCSRef(XhtmlNode x, String url) {
495    CodeSystem cs = getContext().getWorker().fetchCodeSystem(url);
496    if (cs == null) {
497      x.code(url);
498    } else if (cs.hasWebPath()) {
499      x.ah(context.prefixLocalHref(cs.getWebPath())).tx(cs.present());
500    } else {
501      x.code(url);
502      x.tx(" ("+cs.present()+")");
503    }
504  }
505
506  @SuppressWarnings("rawtypes")
507  private void generateVersionNotice(XhtmlNode x, ValueSetExpansionComponent expansion, Resource vs) {
508
509    Multimap<String, String> versions = HashMultimap.create();
510    Set<String> vlist = new HashSet<>();
511    for (ValueSetExpansionParameterComponent p : expansion.getParameter()) {
512      if ((p.getName().startsWith("used-") || p.getName().equals("version")) && !vlist.contains(p.getValue().primitiveValue())) {
513        String name = p.getName().equals("version") ? "system" : p.getName().substring(5);
514        vlist.add(p.getValue().primitiveValue());
515        String[] parts = ((PrimitiveType) p.getValue()).asStringValue().split("\\|");
516        if (parts.length == 2 && !Utilities.noString(parts[0]))
517          versions.put(name+"|"+parts[0], parts[1]);
518      }
519    }
520    if (versions.size() > 0) {
521      XhtmlNode div = null;
522      XhtmlNode ul = null;
523      boolean first = true;
524      for (String s : Utilities.sorted(versions.keySet())) {
525        if (versions.size() == 1 && versions.get(s).size() == 1) {
526          for (String v : versions.get(s)) { // though there'll only be one
527            XhtmlNode p = x.para().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px");
528            if (!vs.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
529              p.tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSION)+" ");
530            } else if ("internal".equals(vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE))) {
531              p.tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSION_INTERNAL)+" ");              
532            } else {
533              p.tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSION_SRVR, vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE))+" ");
534            }
535            expRef(p, s, v, vs);
536          }
537        } else {
538          for (String v : versions.get(s)) {
539            if (first) {
540              div = x.div().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px");
541              if (!vs.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
542                div.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSIONS));                
543              } else if ("internal".equals(vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE))) {
544                div.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSIONS_INTERNAL));                
545              } else {
546                div.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSIONS_SRVR, vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE)));
547              }
548              ul = div.ul();
549              first = false;
550            }
551            expRef(ul.li(), s, v, vs);
552          }
553        }
554      }
555    }
556  }
557
558  private String findParamValue(List<ValueSetExpansionParameterComponent> list, String name) {
559    for (ValueSetExpansionParameterComponent p : list) {
560      if (name.equals(p.getName())) {
561        return p.getValue().primitiveValue();
562      }
563    }
564    return null;
565  }
566  
567  private void expRef(XhtmlNode x, String u, String v, Resource source) {
568    String t = u.contains("|") ? u.substring(0, u.indexOf("|")) : u;
569    u = u.substring(u.indexOf("|")+1);
570    // TODO Auto-generated method stub
571    if (u.equals("http://snomed.info/sct")) {
572      String[] parts = v.split("\\/");
573      if (parts.length >= 5) {
574        String m = describeModule(parts[4]);
575        if (parts.length == 7) {
576          x.tx(context.formatPhrase(RenderingContext.VALUE_SET_SNOMED_ADD, m, formatSCTDate(parts[6])));
577        } else {
578          x.tx(context.formatPhrase(RenderingContext.VALUE_SET_SNOMED, m));
579        }
580      } else {
581        x.tx(displaySystem(u)+" "+ context.formatPhrase(RenderingContext.GENERAL_VER_LOW) + " " +v);
582      }
583    } else if (u.equals("http://loinc.org")) {
584      String vd = describeLoincVer(v);
585      if (vd != null) {
586        x.tx(context.formatPhrase(RenderingContext.VALUE_SET_LOINCV)+v+" ("+vd+")");
587      } else {
588        x.tx(context.formatPhrase(RenderingContext.VALUE_SET_LOINCV)+v);        
589      }
590    } else if (Utilities.noString(v)) {
591      CanonicalResource cr = (CanonicalResource) getContext().getWorker().fetchResource(Resource.class, u, source);
592      if (cr != null) {
593        if (cr.hasWebPath()) {
594          x.ah(context.prefixLocalHref(cr.getWebPath())).tx(t+" "+cr.present()+" "+ context.formatPhrase(RenderingContext.VALUE_SET_NO_VERSION)+cr.fhirType()+")");          
595        } else {
596          x.tx(t+" "+displaySystem(u)+" "+context.formatPhrase(RenderingContext.VALUE_SET_NO_VERSION)+cr.fhirType()+")");
597        }
598      } else {
599        x.tx(t+" "+displaySystem(u)+" "+ context.formatPhrase(RenderingContext.VALUE_SET_NO_VER));
600      }
601    } else {
602      CanonicalResource cr = (CanonicalResource) getContext().getWorker().fetchResource(Resource.class, u+"|"+v, source);
603      if (cr != null) {
604        if (cr.hasWebPath()) {
605          x.ah(context.prefixLocalHref(cr.getWebPath())).tx(t+" "+cr.present()+" v"+v+" ("+cr.fhirType()+")");          
606        } else {
607          x.tx(t+" "+displaySystem(u)+" v"+v+" ("+cr.fhirType()+")");
608        }
609      } else {
610        x.tx(t+" "+displaySystem(u)+" "+ context.formatPhrase(RenderingContext.GENERAL_VER_LOW)+v);
611      }
612    }
613  }
614
615  private String describeLoincVer(String v) {
616    if ("2.67".equals(v))  return "Dec 2019";
617    if ("2.66".equals(v))  return "Jun 2019";
618    if ("2.65".equals(v))  return "Dec 2018";
619    if ("2.64".equals(v))  return "Jun 2018";
620    if ("2.63".equals(v))  return "Dec 2017";
621    if ("2.61".equals(v))  return "Jun 2017";
622    if ("2.59".equals(v))  return "Feb 2017";
623    if ("2.58".equals(v))  return "Dec 2016";
624    if ("2.56".equals(v))  return "Jun 2016";
625    if ("2.54".equals(v))  return "Dec 2015";
626    if ("2.52".equals(v))  return "Jun 2015";
627    if ("2.50".equals(v))  return "Dec 2014";
628    if ("2.48".equals(v))  return "Jun 2014";
629    if ("2.46".equals(v))  return "Dec 2013";
630    if ("2.44".equals(v))  return "Jun 2013";
631    if ("2.42".equals(v))  return "Dec 2012";
632    if ("2.40".equals(v))  return "Jun 2012";
633    if ("2.38".equals(v))  return "Dec 2011";
634    if ("2.36".equals(v))  return "Jun 2011";
635    if ("2.34".equals(v))  return "Dec 2010";
636    if ("2.32".equals(v))  return "Jun 2010";
637    if ("2.30".equals(v))  return "Feb 2010";
638    if ("2.29".equals(v))  return "Dec 2009";
639    if ("2.27".equals(v))  return "Jul 2009";
640    if ("2.26".equals(v))  return "Jan 2009";
641    if ("2.24".equals(v))  return "Jul 2008";
642    if ("2.22".equals(v))  return "Dec 2007";
643    if ("2.21".equals(v))  return "Jun 2007";
644    if ("2.19".equals(v))  return "Dec 2006";
645    if ("2.17".equals(v))  return "Jun 2006";
646    if ("2.16".equals(v))  return "Dec 2005";
647    if ("2.15".equals(v))  return "Jun 2005";
648    if ("2.14".equals(v))  return "Dec 2004";
649    if ("2.13".equals(v))  return "Aug 2004";
650    if ("2.12".equals(v))  return "Feb 2004";
651    if ("2.10".equals(v))  return "Oct 2003";
652    if ("2.09".equals(v))  return "May 2003";
653    if ("2.08 ".equals(v)) return "Sep 2002";
654    if ("2.07".equals(v))  return "Aug 2002";
655    if ("2.05".equals(v))  return "Feb 2002";
656    if ("2.04".equals(v))  return "Jan 2002";
657    if ("2.03".equals(v))  return "Jul 2001";
658    if ("2.02".equals(v))  return "May 2001";
659    if ("2.01".equals(v))  return "Jan 2001";
660    if ("2.00".equals(v))  return "Jan 2001";
661    if ("1.0n".equals(v))  return "Feb 2000";
662    if ("1.0ma".equals(v)) return "Aug 1999";
663    if ("1.0m".equals(v))  return "Jul 1999";
664    if ("1.0l".equals(v))  return "Jan 1998";
665    if ("1.0ja".equals(v)) return "Oct 1997";
666    return null;
667  }
668
669  private String formatSCTDate(String ds) {
670    SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
671    Date date;
672    try {
673      date = format.parse(ds);
674    } catch (ParseException e) {
675      return ds;
676    }
677    return new SimpleDateFormat("dd-MMM yyyy").format(date);
678  }
679
680  private String describeModule(String module) {
681    switch (module) {
682    case "900000000000207008" : context.formatPhrase(RenderingContext.VALUE_SET_INT);
683    case "449081005" : context.formatPhrase(RenderingContext.VALUE_SET_SPAN);  
684    case "11000221109" : context.formatPhrase(RenderingContext.VALUE_SET_AR);
685    case "32506021000036107" : context.formatPhrase(RenderingContext.VALUE_SET_AUS);
686    case "11000234105" : context.formatPhrase(RenderingContext.VALUE_SET_AT);
687    case "11000172109" : context.formatPhrase(RenderingContext.VALUE_SET_BE);
688    case "20621000087109" : context.formatPhrase(RenderingContext.VALUE_SET_CA_EN);
689    case "20611000087101" : context.formatPhrase(RenderingContext.VALUE_SET_CA_FR);
690    case "554471000005108" : context.formatPhrase(RenderingContext.VALUE_SET_DANISH);
691    case "11000181102 " : context.formatPhrase(RenderingContext.VALUE_SET_EE);
692    case "11000229106" : context.formatPhrase(RenderingContext.VALUE_SET_FI);
693    case "11000274103" : context.formatPhrase(RenderingContext.VALUE_SET_DE);
694    case "1121000189102" : context.formatPhrase(RenderingContext.VALUE_SET_IN);
695    case "11000220105" : context.formatPhrase(RenderingContext.VALUE_SET_IE);
696    case "11000146104" : context.formatPhrase(RenderingContext.VALUE_SET_DUTCH);
697    case "21000210109" : context.formatPhrase(RenderingContext.VALUE_SET_NZ);
698    case "51000202101 " : context.formatPhrase(RenderingContext.VALUE_SET_NO);
699    case "11000267109" : context.formatPhrase(RenderingContext.VALUE_SET_KR);
700    case "900000001000122104" : context.formatPhrase(RenderingContext.VALUE_ES_ES);
701    case "45991000052106" : context.formatPhrase(RenderingContext.VALUE_SET_SWEDISH); 
702    case "2011000195101" : context.formatPhrase(RenderingContext.VALUE_SET_CH);
703    case "83821000000107" : context.formatPhrase(RenderingContext.VALUE_SET_UK);
704    case "999000021000000109" : context.formatPhrase(RenderingContext.VALUE_SET_UK_CLIN);
705    case "5631000179106" : context.formatPhrase(RenderingContext.VALUE_SET_UY);  
706    case "731000124108" : context.formatPhrase(RenderingContext.VALUE_SET_US);
707    case "5991000124107" : context.formatPhrase(RenderingContext.VALUE_SET_US_ICD10CM);
708    default:
709      return module;
710    }
711  }
712
713  private boolean hasVersionParameter(ValueSetExpansionComponent expansion) {
714    for (ValueSetExpansionParameterComponent p : expansion.getParameter()) {
715      if (p.getName().equals("version"))
716        return true;
717    }
718    return false;
719  }
720
721  private void addDesignationRow(ValueSetExpansionContainsComponent c, XhtmlNode t, List<String> langs, Map<String, String> designations) {
722    XhtmlNode tr = t.tr();
723    tr.td().addText(c.getCode());
724    addDesignationsToRow(c, designations, tr);
725    addLangaugesToRow(c, langs, tr);
726    for (ValueSetExpansionContainsComponent cc : c.getContains()) {
727      addDesignationRow(cc, t, langs, designations);
728    }
729  }
730
731  public void addDesignationsToRow(ValueSetExpansionContainsComponent c, Map<String, String> designations, XhtmlNode tr) {
732    for (String url : designations.keySet()) {
733      String d = null;
734      if (d == null) {
735        for (ConceptReferenceDesignationComponent dd : c.getDesignation()) {
736          if (url.equals(getUrlForDesignation(dd))) {
737            d = dd.getValue();
738          }
739        }
740      }
741      tr.td().addText(d == null ? "" : d);
742    }
743  }
744
745  public void addLangaugesToRow(ValueSetExpansionContainsComponent c, List<String> langs, XhtmlNode tr) {
746    for (String lang : langs) {
747      String d = null;
748      for (Extension ext : c.getExtension()) {
749        if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) {
750          String l = ToolingExtensions.readStringExtension(ext, "lang");
751          if (lang.equals(l)) {
752            d = ToolingExtensions.readStringExtension(ext, "content");
753          }
754        }
755      }
756      if (d == null) {
757        for (ConceptReferenceDesignationComponent dd : c.getDesignation()) {
758          String l = dd.getLanguage();
759          if (lang.equals(l)) {
760            d = dd.getValue();
761          }
762        }
763      }
764      tr.td().addText(d == null ? "" : d);
765    }
766  }
767
768  
769  private boolean checkDoDefinition(List<ValueSetExpansionContainsComponent> contains) {
770    for (ValueSetExpansionContainsComponent c : contains) {
771      CodeSystem cs = getContext().getWorker().fetchCodeSystem(c.getSystem());
772      if (cs != null) {
773        ConceptDefinitionComponent cd = CodeSystemUtilities.getCode(cs, c.getCode());
774        if (cd != null && cd.hasDefinition()) {
775          return true;
776        }
777      }
778      if (checkDoDefinition(c.getContains()))
779        return true;
780    }
781    return false;
782  }
783
784  private boolean checkDoInactive(List<ValueSetExpansionContainsComponent> contains) {
785    for (ValueSetExpansionContainsComponent c : contains) {
786      if (c.hasInactive()) {
787        return true;
788      }
789      if (checkDoInactive(c.getContains()))
790        return true;
791    }
792    return false;
793  }
794
795
796  private boolean allFromOneSystem(ValueSet vs) {
797    if (vs.getExpansion().getContains().isEmpty())
798      return false;
799    String system = vs.getExpansion().getContains().get(0).getSystem();
800    for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
801      if (!checkSystemMatches(system, cc))
802        return false;
803    }
804    return true;
805  }
806
807  private String getCsRef(String system) {
808    CodeSystem cs = getContext().getWorker().fetchCodeSystem(system);
809    return getCsRef(cs);
810  }
811
812  private  <T extends Resource> String getCsRef(T cs) {
813    if (cs == null) {
814      return "?cs-n?";
815    }
816    String ref = cs.getWebPath();
817    if (ref == null) {
818      ref = cs.getUserString(UserDataNames.render_filename);
819    }
820    return ref == null ? null : ref.replace("\\", "/");
821  }
822
823  private void scanForDesignations(ValueSetExpansionContainsComponent c, List<String> langs, Map<String, String> designations) {
824    for (Extension ext : c.getExtension()) {
825      if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) {
826        String lang = ToolingExtensions.readStringExtension(ext,  "lang");
827        if (!Utilities.noString(lang) && !langs.contains(lang)) {
828          langs.add(lang);
829        }
830      }
831    }
832    for (ConceptReferenceDesignationComponent d : c.getDesignation()) {
833      String lang = d.getLanguage();
834      if (!Utilities.noString(lang) && !langs.contains(lang)) {
835        langs.add(lang);
836      } else {
837        // can we present this as a designation that we know?
838        String disp = getDisplayForDesignation(d);
839        String url = getUrlForDesignation(d);
840        if (disp == null) {
841          disp = getDisplayForUrl(url);
842        }
843        if (disp != null && !designations.containsKey(url) && url != null) {
844          designations.put(url, disp);
845        }
846      }
847    }
848    for (ValueSetExpansionContainsComponent cc : c.getContains()) {
849      scanForDesignations(cc, langs, designations);
850    }
851  }
852
853  private void scanForLangs(ValueSetExpansionContainsComponent c, List<String> langs) {
854    for (Extension ext : c.getExtension()) {
855      if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) {
856        String lang = ToolingExtensions.readStringExtension(ext,  "lang");
857        if (!Utilities.noString(lang) && !langs.contains(lang)) {
858          langs.add(lang);
859        }
860      }
861    }
862    for (ConceptReferenceDesignationComponent d : c.getDesignation()) {
863      String lang = d.getLanguage();
864      if (!Utilities.noString(lang) && !langs.contains(lang)) {
865        langs.add(lang);
866      }
867    }
868    for (ValueSetExpansionContainsComponent cc : c.getContains()) {
869      scanForLangs(cc, langs);
870    }    
871  }
872
873  private void addExpansionRowToTable(XhtmlNode t, ValueSet vs, ValueSetExpansionContainsComponent c, int i, boolean doLevel, boolean doDefinition, boolean doInactive, List<UsedConceptMap> maps, List<String> langs, Map<String, String> designations, boolean doDesignations, Map<String, String> properties, ResourceWrapper res) throws FHIRFormatError, DefinitionException, IOException {
874    XhtmlNode tr = t.tr();
875    if (ValueSetUtilities.isDeprecated(vs, c)) {
876      tr.setAttribute("style", "background-color: #ffeeee");
877    }
878      
879    XhtmlNode td = tr.td();
880
881    String tgt = makeAnchor(c.getSystem(), c.getCode());
882    String pfx = res.getScopedId();
883    td.an((context.prefixAnchor(pfx == null ? "" : pfx+"-")+tgt));
884
885    if (doLevel) {
886      td.addText(Integer.toString(i));
887      td = tr.td();
888    }
889    String s = Utilities.padLeft("", '\u00A0', i*2);
890    td.attribute("style", "white-space:nowrap").addText(s);
891    addCodeToTable(c.getAbstract(), c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay(), td);
892    td = tr.td();
893    td.addText(c.getSystem());
894    td = tr.td();
895    if (c.hasDisplayElement())
896      td.addText(c.getDisplay());
897
898    if (doInactive) {
899      td = tr.td();
900      if (c.getInactive()) {
901        td.tx(context.formatPhrase(RenderingContext.VALUE_SET_INACT));
902      }
903    }
904    if (doDefinition) {
905      td = tr.td();
906      CodeSystem cs = getContext().getWorker().fetchCodeSystem(c.getSystem());
907      if (cs != null) {
908        String defn = CodeSystemUtilities.getCodeDefinition(cs, c.getCode());
909        addMarkdown(td, defn, cs.getWebPath());
910      }
911    }
912    for (String n  : Utilities.sorted(properties.keySet())) {
913      td = tr.td();
914      String ps = getPropertyValue(c, n); 
915      if (!Utilities.noString(ps)) {  
916        td.addText(ps);        
917      }
918    }
919    for (UsedConceptMap m : maps) {
920      td = tr.td();
921      List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap());
922      boolean first = true;
923      for (TargetElementComponentWrapper mapping : mappings) {
924        if (!first)
925            td.br();
926        first = false;
927        XhtmlNode span = td.span(null, mapping.comp.getRelationship().toString());
928        span.addText(getCharForRelationship(mapping.comp));
929        addRefToCode(td, mapping.group.getTarget(), null, m.getLink(), mapping.comp.getCode()); 
930        if (!Utilities.noString(mapping.comp.getComment()))
931          td.i().tx("("+mapping.comp.getComment()+")");
932      }
933    }
934    if (doDesignations) {
935      addDesignationsToRow(c, designations, tr);
936      addLangaugesToRow(c, langs, tr);
937    }
938    for (ValueSetExpansionContainsComponent cc : c.getContains()) {
939      addExpansionRowToTable(t, vs, cc, i+1, doLevel, doDefinition, doInactive, maps, langs, designations, doDesignations, properties, res);
940    }
941  }
942
943
944
945
946
947  private String getPropertyValue(ValueSetExpansionContainsComponent c, String n) {
948    for (ConceptPropertyComponent  cp : c.getProperty()) {
949      if (n.equals(cp.getCode())) {
950        return cp.getValue().primitiveValue();
951      }
952    }
953    return null;
954  }
955
956  private boolean checkSystemMatches(String system, ValueSetExpansionContainsComponent cc) {
957    if (!system.equals(cc.getSystem()))
958      return false;
959    for (ValueSetExpansionContainsComponent cc1 : cc.getContains()) {
960      if (!checkSystemMatches(system, cc1))
961        return false;
962    }
963     return true;
964  }
965
966  private void addCodeToTable(boolean isAbstract, String system, String version, String code, String display, XhtmlNode td) {
967    CodeSystem e = getContext().getWorker().fetchCodeSystem(system);
968    if (e == null || (e.getContent() != org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode.COMPLETE && e.getContent() != org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode.FRAGMENT)) {
969      if (isAbstract)
970        td.i().setAttribute("title", context.formatPhrase(RenderingContext.VS_ABSTRACT_CODE_HINT)).addText(code);
971      else if ("http://snomed.info/sct".equals(system)) {
972        td.ah(context.prefixLocalHref(SnomedUtilities.getSctLink(version, code, context.getContext().getExpansionParameters()))).addText(code);
973      } else if ("http://loinc.org".equals(system)) {
974          td.ah(context.prefixLocalHref(LoincLinker.getLinkForCode(code))).addText(code);
975      } else        
976        td.addText(code);
977    } else {
978      String href = context.fixReference(getCsRef(e));
979      if (href == null) {
980        td.code().tx(code);        
981      } else {
982        if (href.contains("#"))
983          href = href + "-"+Utilities.nmtokenize(code);
984        else
985          href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(code);
986        if (isAbstract)
987          td.ah(context.prefixLocalHref(href)).setAttribute("title", context.formatPhrase(RenderingContext.VS_ABSTRACT_CODE_HINT)).i().addText(code);
988        else
989          td.ah(context.prefixLocalHref(href)).addText(code);
990      }
991    }
992  }
993
994  private void addRefToCode(XhtmlNode td, String target, String vslink, String code, String version) {
995    addCodeToTable(false, target, version, code, null, td);
996//    CodeSystem cs = getContext().getWorker().fetchCodeSystem(target);
997//    String cslink = getCsRef(cs);
998//    String link = cslink != null ? cslink+"#"+cs.getId()+"-"+code : vslink+"#"+code;
999//    if (!Utilities.isAbsoluteUrl(link)) {
1000//      link = getContext().getSpecificationLink()+link;
1001//    }
1002//    XhtmlNode a = td.ah(context.prefixLocalHref(link));
1003//    a.addText(code);
1004  }
1005
1006  private void generateComposition(RenderingStatus status, ResourceWrapper res, XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRException, IOException {
1007    List<String> langs = new ArrayList<String>();
1008    Map<String, String> designations = new HashMap<>(); //  map of url = description, where url is the designation code. Designations that are for languages won't make it into this list 
1009    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1010      scanDesignations(inc, langs, designations);
1011    }
1012    for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1013      scanDesignations(inc, langs, designations);
1014    }
1015    boolean doDesignations = langs.size() + designations.size() < MAX_DESIGNATIONS_IN_LINE;
1016    
1017    if (header) {
1018      XhtmlNode h = x.h2();
1019      h.addText(vs.present());
1020      addMarkdown(x, vs.getDescription());
1021      if (vs.hasCopyrightElement())
1022        generateCopyright(x, res);
1023    }
1024    int index = 0;
1025    if (vs.getCompose().getInclude().size() == 1 && vs.getCompose().getExclude().size() == 0 && !VersionComparisonAnnotation.hasDeleted(vs.getCompose(), "include", "exclude")) {
1026      genInclude(status, x.ul(), vs.getCompose().getInclude().get(0), "Include", langs, doDesignations, maps, designations, index, vs);
1027    } else {
1028      XhtmlNode p = x.para();
1029      p.tx(context.formatPhrase(RenderingContext.VALUE_SET_RULES_INC));
1030      XhtmlNode ul = x.ul();
1031      for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1032        genInclude(status, ul, inc, context.formatPhrase(RenderingContext.VALUE_SET_INC), langs, doDesignations, maps, designations, index, vs);
1033        index++;
1034      }
1035      for (Base inc : VersionComparisonAnnotation.getDeleted(vs.getCompose(), "include")) {
1036        genInclude(status, ul, (ConceptSetComponent) inc, context.formatPhrase(RenderingContext.VALUE_SET_INC), langs, doDesignations, maps, designations, index, vs);
1037        index++;
1038      }
1039      if (vs.getCompose().hasExclude() || VersionComparisonAnnotation.hasDeleted(vs.getCompose(), "exclude")) {
1040        p = x.para();
1041        p.tx(context.formatPhrase(RenderingContext.VALUE_SET_RULES_EXC));
1042        ul = x.ul();
1043        for (ConceptSetComponent exc : vs.getCompose().getExclude()) {
1044          genInclude(status, ul, exc, context.formatPhrase(RenderingContext.VALUE_SET_EXCL), langs, doDesignations, maps, designations, index, vs);
1045          index++;
1046        }
1047        for (Base inc : VersionComparisonAnnotation.getDeleted(vs.getCompose(), "exclude")) {
1048          genInclude(status, ul, (ConceptSetComponent) inc, context.formatPhrase(RenderingContext.VALUE_SET_EXCL), langs, doDesignations, maps, designations, index, vs);
1049          index++;
1050        }
1051      }
1052    }
1053    
1054    // now, build observed languages
1055
1056    if (!doDesignations && langs.size() + designations.size() > 0) {
1057      Collections.sort(langs);
1058      if (designations.size() == 0) {
1059        x.para().b().tx(context.formatPhrase(RenderingContext.GENERAL_ADD_LANG));        
1060      } else if (langs.size() == 0) {
1061        x.para().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_DESIG));       
1062      } else {
1063        x.para().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_ADD_DESIG));
1064      }
1065      XhtmlNode t = x.table("codes", false);
1066      XhtmlNode tr = t.tr();
1067      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE));
1068      for (String url : designations.keySet()) {
1069        tr.td().b().addText(designations.get(url));
1070      }
1071      for (String lang : langs) {
1072        tr.td().b().addText(describeLang(lang));
1073      }
1074      for (ConceptSetComponent c : vs.getCompose().getInclude()) {
1075        for (ConceptReferenceComponent cc : c.getConcept()) {
1076          addDesignationRow(cc, t, langs, designations);
1077        }
1078      }
1079    }
1080  }
1081
1082  private void renderExpansionRules(XhtmlNode x, ConceptSetComponent inc, int index, Map<String, ConceptDefinitionComponent> definitions) throws FHIRException, IOException {
1083    String s = context.formatPhrase(RenderingContext.VALUE_SET_NOT_DEF);
1084    if (inc.hasExtension(ToolingExtensions.EXT_EXPAND_RULES)) {
1085      String rule = inc.getExtensionString(ToolingExtensions.EXT_EXPAND_RULES);
1086      if (rule != null) {
1087        switch (rule) {
1088        case "all-codes": s = context.formatPhrase(RenderingContext.VALUE_SET_ALL_CODE); 
1089        case "ungrouped": s = context.formatPhrase(RenderingContext.VALUE_SET_NOT_FOUND);
1090        case "groups-only": s = context.formatPhrase(RenderingContext.VALUE_SET_CONT_STRUC);
1091        }
1092      }
1093    }
1094    x.br();
1095    x.tx(s);
1096    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true, "exp");
1097    TableModel model = gen.new TableModel("exp.h="+index, context.getRules() == GenerationRules.IG_PUBLISHER);    
1098    model.setAlternating(true);
1099    model.getTitles().add(gen.new Title(null, model.getDocoRef(), context.formatPhrase(RenderingContext.GENERAL_CODE), context.formatPhrase(RenderingContext.VALUE_SET_CODE_ITEM), null, 0));
1100    model.getTitles().add(gen.new Title(null, model.getDocoRef(), context.formatPhrase(RenderingContext.TX_DISPLAY), context.formatPhrase(RenderingContext.VALUE_SET_DISPLAY_ITEM), null, 0));
1101
1102    for (Extension ext : inc.getExtensionsByUrl(ToolingExtensions.EXT_EXPAND_GROUP)) {
1103      renderExpandGroup(gen, model, ext, inc, definitions);
1104    }
1105    x.br();
1106    x.tx("table"); 
1107    XhtmlNode xn = gen.generate(model, context.getLocalPrefix(), 1, null);
1108    x.addChildNode(xn);
1109  }
1110
1111  private void renderExpandGroup(HierarchicalTableGenerator gen, TableModel model, Extension ext, ConceptSetComponent inc, Map<String, ConceptDefinitionComponent> definitions) {
1112    Row row = gen.new Row(); 
1113    model.getRows().add(row);
1114    row.setIcon("icon_entry_blue.png", "entry");
1115    String code = ext.getExtensionString("code");
1116    if (code != null) {
1117      row.getCells().add(gen.new Cell(null, null, code, null, null));
1118      row.getCells().add(gen.new Cell(null, null, getDisplayForCode(inc, code, definitions), null, null));
1119    } else if (ext.hasId()) {      
1120      row.getCells().add(gen.new Cell(null, null, "(#"+ext.getId()+")", null, null));      
1121      row.getCells().add(gen.new Cell(null, null, ext.getExtensionString("display"), null, null));
1122    } else {
1123      row.getCells().add(gen.new Cell(null, null, null, null, null));      
1124      row.getCells().add(gen.new Cell(null, null, ext.getExtensionString("display"), null, null));
1125    }
1126    for (Extension member : ext.getExtensionsByUrl("member")) {
1127      Row subRow = gen.new Row(); 
1128      row.getSubRows().add(subRow);
1129      subRow.setIcon("icon_entry_blue.png", "entry");
1130      String mc = member.getValue().primitiveValue();
1131      // mc might be a reference to another expansion group - we check that first, or to a code in the compose
1132      if (mc.startsWith("#")) {
1133        // it's a reference by id
1134        subRow.getCells().add(gen.new Cell(null, null, "("+mc+")", null, null));      
1135        subRow.getCells().add(gen.new Cell(null, null, "group reference by id", null, null));
1136      } else {
1137        Extension tgt = findTargetByCode(inc, mc);
1138        if (tgt != null) {
1139          subRow.getCells().add(gen.new Cell(null, null, mc, null, null));      
1140          subRow.getCells().add(gen.new Cell(null, null, "group reference by code", null, null));                    
1141        } else {
1142          subRow.getCells().add(gen.new Cell(null, null, mc, null, null));      
1143          subRow.getCells().add(gen.new Cell(null, null, getDisplayForCode(inc, mc, definitions), null, null));          
1144        }
1145      }
1146    }
1147  }
1148
1149  private Extension findTargetByCode(ConceptSetComponent inc, String mc) {
1150    for (Extension ext : inc.getExtensionsByUrl(ToolingExtensions.EXT_EXPAND_GROUP)) {
1151      String code = ext.getExtensionString("code");
1152      if (mc.equals(code)) {
1153        return ext;
1154      }
1155    }
1156    return null;
1157  }
1158
1159  private String getDisplayForCode(ConceptSetComponent inc, String code, Map<String, ConceptDefinitionComponent> definitions) {
1160    for (ConceptReferenceComponent cc : inc.getConcept()) {
1161      if (code.equals(cc.getCode())) {
1162        if (cc.hasDisplay()) {
1163          return cc.getDisplay();
1164        }
1165      }
1166    }
1167    if (definitions.containsKey(code)) {
1168      return definitions.get(code).getDisplay();
1169    }
1170    return null;
1171  }
1172
1173  private void scanDesignations(ConceptSetComponent inc, List<String> langs, Map<String, String> designations) {
1174    for (ConceptReferenceComponent cc : inc.getConcept()) {
1175      for (Extension ext : cc.getExtension()) {
1176        if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) {
1177          String lang = ToolingExtensions.readStringExtension(ext,  "lang");
1178          if (!Utilities.noString(lang) && !langs.contains(lang)) {
1179            langs.add(lang);
1180          }
1181        }
1182      }
1183      for (ConceptReferenceDesignationComponent d : cc.getDesignation()) {
1184        String lang = d.getLanguage();
1185        if (!Utilities.noString(lang) && !langs.contains(lang)) {
1186          langs.add(lang);
1187        } else {
1188          // can we present this as a designation that we know?
1189          String disp = getDisplayForDesignation(d);
1190          String url = getUrlForDesignation(d);
1191          if (disp == null) {
1192            disp = getDisplayForUrl(url);
1193          }
1194          if (disp != null && !designations.containsKey(url)) {
1195            designations.put(url, disp);            
1196          }
1197        }
1198      }
1199    }
1200  }
1201
1202  private String getDisplayForUrl(String url) {
1203    if (url == null) {
1204      return null;
1205    }
1206    switch (url) {
1207    case "http://snomed.info/sct#900000000000003001":
1208      return context.formatPhrase(RenderingContext.VALUE_SET_SPEC_NAME);
1209    case "http://snomed.info/sct#900000000000013009":
1210      return context.formatPhrase(RenderingContext.VALUE_SET_SYNONYM);
1211    default:
1212      // As specified in http://www.hl7.org/fhir/valueset-definitions.html#ValueSet.compose.include.concept.designation.use and in http://www.hl7.org/fhir/codesystem-definitions.html#CodeSystem.concept.designation.use the terminology binding is extensible.
1213      return url;
1214    }
1215  }
1216
1217  private String getUrlForDesignation(ConceptReferenceDesignationComponent d) {
1218    if (d.hasUse() && d.getUse().hasSystem() && d.getUse().hasCode()) {
1219      return d.getUse().getSystem()+"#"+d.getUse().getCode();
1220    } else {
1221      return null;
1222    }
1223  }
1224
1225  private String getDisplayForDesignation(ConceptReferenceDesignationComponent d) {
1226    if (d.hasUse() && d.getUse().hasDisplay()) {
1227      return d.getUse().getDisplay();
1228    } else {
1229      return null;
1230    }
1231  }
1232
1233  private void genInclude(RenderingStatus status, XhtmlNode ul, ConceptSetComponent inc, String type, List<String> langs, boolean doDesignations, List<UsedConceptMap> maps, Map<String, String> designations, int index, ValueSet vsRes) throws FHIRException, IOException {
1234    XhtmlNode li;
1235    li = ul.li();
1236    li = renderStatus(inc, li);
1237
1238    Map<String, ConceptDefinitionComponent> definitions = new HashMap<>();
1239    
1240    if (inc.hasSystem()) {
1241      CodeSystem e = getContext().getWorker().fetchCodeSystem(inc.getSystem());
1242      if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
1243        li.addText(type+" "+ context.formatPhrase(RenderingContext.VALUE_SET_ALL_CODES_DEF) + " ");
1244        addCsRef(inc, li, e);
1245      } else {
1246        if (inc.getConcept().size() > 0) {
1247          li.addText(type+" "+ context.formatPhrase(RenderingContext.VALUE_SET_THESE_CODES_DEF) + " ");
1248          addCsRef(inc, li, e);
1249          if (inc.hasVersion()) {
1250            li.addText(" "+ context.formatPhrase(RenderingContext.GENERAL_VER_LOW) + " ");
1251            li.code(inc.getVersion());  
1252          }
1253
1254          // for performance reasons, we do all the fetching in one batch
1255          definitions = getConceptsForCodes(e, inc, vsRes, index);
1256
1257          
1258          XhtmlNode t = li.table("none", false);
1259          boolean hasComments = false;
1260          boolean hasDefinition = false;
1261          for (ConceptReferenceComponent c : inc.getConcept()) {
1262            hasComments = hasComments || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT);
1263            ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode()); 
1264            hasDefinition = hasDefinition || ((cc != null && cc.hasDefinition()) || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION));
1265          }
1266          if (hasComments || hasDefinition) {
1267            status.setExtensions(true);
1268          }
1269          addMapHeaders(addTableHeaderRowStandard(t, false, true, hasDefinition, hasComments, false, false, null, langs, designations, doDesignations), maps);
1270          for (ConceptReferenceComponent c : inc.getConcept()) {
1271            renderConcept(inc, langs, doDesignations, maps, designations, definitions, t, hasComments, hasDefinition, c, inc.getVersion());
1272          }
1273          for (Base b : VersionComparisonAnnotation.getDeleted(inc, "concept" )) {
1274            renderConcept(inc, langs, doDesignations, maps, designations, definitions, t, hasComments, hasDefinition, (ConceptReferenceComponent) b, inc.getVersion());          
1275          }
1276        }
1277        if (inc.getFilter().size() > 0) {
1278          li.addText(type+" "+ context.formatPhrase(RenderingContext.VALUE_SET_CODES_FROM));
1279          addCsRef(inc, li, e);
1280          li.tx(" "+ context.formatPhrase(RenderingContext.VALUE_SET_WHERE)+" ");
1281          for (int i = 0; i < inc.getFilter().size(); i++) {
1282            ConceptSetFilterComponent f = inc.getFilter().get(i);
1283            if (i > 0) {
1284              if (i == inc.getFilter().size()-1) {
1285                li.tx(" "+ context.formatPhrase(RenderingContext.VALUE_SET_AND)+" ");
1286              } else {
1287                li.tx(context.formatPhrase(RenderingContext.VALUE_SET_COMMA)+" ");
1288              }
1289            }
1290            XhtmlNode wli = renderStatus(f, li);
1291            if (f.getOp() == FilterOperator.EXISTS) {
1292              if (f.getValue().equals("true")) {
1293                wli.tx(f.getProperty()+" "+ context.formatPhrase(RenderingContext.VALUE_SET_EXISTS));
1294              } else {
1295                wli.tx(f.getProperty()+" "+ context.formatPhrase(RenderingContext.VALUE_SET_DOESNT_EXIST));
1296              }
1297            } else {
1298              wli.tx(f.getProperty()+" "+describe(f.getOp())+" ");
1299              if (f.getValueElement().hasExtension(ToolingExtensions.EXT_CQF_EXP)) {
1300                Extension expE = f.getValueElement().getExtensionByUrl(ToolingExtensions.EXT_CQF_EXP);
1301                Expression exp = expE.getValueExpression();
1302                wli.addText("(as calculated by ");
1303                wli.code().tx(exp.getExpression());
1304                wli.addText(")");
1305              } else {
1306                if (e != null && codeExistsInValueSet(e, f.getValue())) {
1307                  String href = getContext().fixReference(getCsRef(e));
1308                  if (href == null) {
1309                    wli.code().tx(f.getValue());                  
1310                  } else {
1311                    if (href.contains("#"))
1312                      href = href + "-"+Utilities.nmtokenize(f.getValue());
1313                    else
1314                      href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue());
1315                    wli.ah(context.prefixLocalHref(href)).addText(f.getValue());
1316                  }
1317                } else if (inc.hasSystem()) {
1318                  wli.addText(f.getValue());
1319                  ValidationResult vr = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions(), inc.getSystem(), inc.getVersion(), f.getValue(), null);
1320                  if (vr.isOk() && vr.getDisplay() != null) {
1321                    wli.tx(" ("+vr.getDisplay()+")");
1322                  }
1323                } else {
1324                  wli.addText(f.getValue());
1325                }
1326              }
1327              String disp = ToolingExtensions.getDisplayHint(f);
1328              if (disp != null)
1329                wli.tx(" ("+disp+")");
1330            }
1331          }
1332        }
1333      }
1334      if (inc.hasValueSet()) {
1335        li.tx(context.formatPhrase(RenderingContext.VALUE_SET_WHERE_CODES)+" ");
1336        boolean first = true;
1337        for (UriType vs : inc.getValueSet()) {
1338          if (first)
1339            first = false;
1340          else
1341            li.tx(", ");
1342          XhtmlNode wli = renderStatus(vs, li);
1343          AddVsRef(vs.asStringValue(), wli, vsRes);
1344        }
1345      }
1346      if (inc.hasExtension(ToolingExtensions.EXT_EXPAND_RULES) || inc.hasExtension(ToolingExtensions.EXT_EXPAND_GROUP)) {
1347        status.setExtensions(true);
1348        renderExpansionRules(li, inc, index, definitions);
1349      }
1350    } else {
1351      li.tx(context.formatMessagePlural(inc.getValueSet().size(), RenderingContext.VALUE_SET_IMPORT)+" ");
1352      if (inc.getValueSet().size() <= 2) {
1353        int i = 0;  
1354        for (UriType vs : inc.getValueSet()) {
1355          if (i > 0) {
1356            if ( i  < inc.getValueSet().size() - 1) {
1357              li.tx(", ");
1358            } else {
1359              li.tx(" and ");              
1360            }
1361          }
1362          i++;
1363          XhtmlNode wli = renderStatus(vs, li);
1364          AddVsRef(vs.asStringValue(), wli, vsRes);
1365        }
1366      } else {
1367        XhtmlNode xul = li.ul();
1368        for (UriType vs : inc.getValueSet()) {
1369          XhtmlNode wli = renderStatus(vs,  xul.li());
1370          AddVsRef(vs.asStringValue(), wli, vsRes);
1371        }
1372        
1373      }
1374    }
1375  }
1376
1377  private void renderConcept(ConceptSetComponent inc, List<String> langs, boolean doDesignations,
1378      List<UsedConceptMap> maps, Map<String, String> designations, Map<String, ConceptDefinitionComponent> definitions,
1379      XhtmlNode t, boolean hasComments, boolean hasDefinition, ConceptReferenceComponent c, String version) {
1380    XhtmlNode tr = t.tr();
1381    XhtmlNode td = renderStatusRow(c, t, tr);
1382    ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode()); 
1383    addCodeToTable(false, inc.getSystem(), version, c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td);
1384
1385    td = tr.td();
1386    if (!Utilities.noString(c.getDisplay()))
1387      renderStatus(c.getDisplayElement(), td).addText(c.getDisplay());
1388    else if (VersionComparisonAnnotation.hasDeleted(c, "display")) {
1389      StringType d = (StringType) VersionComparisonAnnotation.getDeletedItem(c, "display"); 
1390      renderStatus(d, td).addText(d.primitiveValue());
1391    } else if (cc != null && !Utilities.noString(cc.getDisplay()))
1392      td.style("color: #cccccc").addText(cc.getDisplay());
1393
1394    if (hasDefinition) {
1395      td = tr.td();
1396      if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION)) {
1397        smartAddText(td, ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_DEFINITION));
1398      } else if (cc != null && !Utilities.noString(cc.getDefinition())) {
1399        smartAddText(td, cc.getDefinition());
1400      }
1401    }
1402    if (hasComments) {
1403      td = tr.td();
1404      if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT)) {
1405        smartAddText(td, context.formatPhrase(RenderingContext.VALUE_SET_NOTE, ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_VS_COMMENT)+" "));
1406      }
1407    }
1408    if (doDesignations) {
1409      addDesignationsToRow(c, designations, tr);
1410      addLangaugesToRow(c, langs, tr);
1411    }
1412    for (UsedConceptMap m : maps) {
1413      td = tr.td();
1414      List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap());
1415      boolean first = true;
1416      for (TargetElementComponentWrapper mapping : mappings) {
1417        if (!first)
1418            td.br();
1419        first = false;
1420        XhtmlNode span = td.span(null, mapping.comp.getRelationship().toString());
1421        span.addText(getCharForRelationship(mapping.comp));
1422        addRefToCode(td, mapping.group.getTarget(), m.getLink(), mapping.comp.getCode(), version); 
1423        if (!Utilities.noString(mapping.comp.getComment()))
1424          td.i().tx("("+mapping.comp.getComment()+")");
1425      }
1426    }
1427  }
1428
1429  public void addDesignationsToRow(ConceptReferenceComponent c, Map<String, String> designations, XhtmlNode tr) {
1430    for (String url : designations.keySet()) {
1431      String d = null;
1432      if (d == null) {
1433        for (ConceptReferenceDesignationComponent dd : c.getDesignation()) {
1434          if (url.equals(getUrlForDesignation(dd))) {
1435            d = dd.getValue();
1436          }
1437        }
1438      }
1439      tr.td().addText(d == null ? "" : d);
1440    }
1441  }
1442
1443  public void addLangaugesToRow(ConceptReferenceComponent c, List<String> langs, XhtmlNode tr) {
1444    for (String lang : langs) {
1445      String d = null;
1446      for (Extension ext : c.getExtension()) {
1447        if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) {
1448          String l = ToolingExtensions.readStringExtension(ext, "lang");
1449          if (lang.equals(l)) {
1450            d = ToolingExtensions.readStringExtension(ext, "content");
1451          }
1452        }
1453      }
1454      if (d == null) {
1455        for (ConceptReferenceDesignationComponent dd : c.getDesignation()) {
1456          String l = dd.getLanguage();
1457          if (lang.equals(l)) {
1458            d = dd.getValue();
1459          }
1460        }
1461      }
1462      tr.td().addText(d == null ? "" : d);
1463    }
1464  }
1465
1466
1467  private Map<String, ConceptDefinitionComponent> getConceptsForCodes(CodeSystem e, ConceptSetComponent inc, ValueSet source, int index) {
1468    if (e == null) {
1469      e = getContext().getWorker().fetchCodeSystem(inc.getSystem());
1470    }
1471    
1472    ValueSetExpansionComponent vse = null;
1473    if (!context.isNoSlowLookup()) { // && !getContext().getWorker().hasCache()) { removed GG 20220107 like what is this trying to do?
1474      try {
1475        
1476        ValueSet vs = new ValueSet();
1477        vs.setUrl(source.getUrl()+"-inc-"+index);
1478        vs.setStatus(PublicationStatus.ACTIVE);
1479        vs.setCompose(new ValueSetComposeComponent());
1480        vs.getCompose().setInactive(false);
1481        vs.getCompose().getInclude().add(inc);
1482        
1483        ValueSetExpansionOutcome vso = getContext().getWorker().expandVS(vs, true, false);
1484        ValueSet valueset = vso.getValueset();
1485        if (valueset == null)
1486          throw new TerminologyServiceException(context.formatPhrase(RenderingContext.VALUE_SET_ERROR, vso.getError()+" "));
1487        vse = valueset.getExpansion();        
1488
1489      } catch (Exception e1) {
1490        return null;
1491      }
1492    }
1493    
1494    Map<String, ConceptDefinitionComponent> results = new HashMap<>();
1495    List<CodingValidationRequest> serverList = new ArrayList<>();
1496    
1497    // 1st pass, anything we can resolve internally
1498    for (ConceptReferenceComponent cc : inc.getConcept()) {
1499      String code = cc.getCode();
1500      ConceptDefinitionComponent v = null;
1501      if (e != null && code != null) {
1502        v = getConceptForCode(e.getConcept(), code);
1503      }
1504      if (v == null && vse != null) {
1505        v = getConceptForCodeFromExpansion(vse.getContains(), code);
1506      }
1507      if (v != null) {
1508        results.put(code, v);
1509      } else {
1510        serverList.add(new CodingValidationRequest(new Coding(inc.getSystem(), code, null)));
1511      }
1512    }
1513    if (!context.isNoSlowLookup() && !serverList.isEmpty()) {
1514      try {
1515        // todo: split this into 10k batches 
1516        int i = 0;
1517        while (serverList.size() > i) { 
1518          int len = Integer.min(serverList.size(), MAX_BATCH_VALIDATION_SIZE);
1519          List<CodingValidationRequest> list = serverList.subList(i, i+len);
1520          i += len;
1521          getContext().getWorker().validateCodeBatch(getContext().getTerminologyServiceOptions(), list, null);
1522          for (CodingValidationRequest vr : list) {
1523            ConceptDefinitionComponent v = vr.getResult().asConceptDefinition();
1524            if (v != null) {
1525              results.put(vr.getCoding().getCode(), v);
1526            }
1527          }
1528        }
1529      } catch (Exception e1) {
1530        return null;
1531      }
1532    }
1533    return results;
1534  }
1535  
1536  private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> list, String code) {
1537    for (ConceptDefinitionComponent c : list) {
1538    if (code.equals(c.getCode()))
1539      return c;
1540      ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code);
1541      if (v != null)
1542        return v;
1543    }
1544    return null;
1545  }
1546
1547  private ConceptDefinitionComponent getConceptForCodeFromExpansion(List<ValueSetExpansionContainsComponent> list, String code) {
1548    for (ValueSetExpansionContainsComponent c : list) {
1549      if (code.equals(c.getCode())) {
1550        ConceptDefinitionComponent res = new ConceptDefinitionComponent();
1551        res.setCode(c.getCode());
1552        res.setDisplay(c.getDisplay());
1553        return res;
1554      }
1555      ConceptDefinitionComponent v = getConceptForCodeFromExpansion(c.getContains(), code);
1556      if (v != null)
1557        return v;
1558    }
1559    return null;
1560  }
1561
1562 
1563  private boolean codeExistsInValueSet(CodeSystem cs, String code) {
1564    for (ConceptDefinitionComponent c : cs.getConcept()) {
1565      if (inConcept(code, c))
1566        return true;
1567    }
1568    return false;
1569  }
1570  
1571
1572
1573  private void addDesignationRow(ConceptReferenceComponent c, XhtmlNode t, List<String> langs, Map<String, String> designations) {
1574    XhtmlNode tr = t.tr();
1575    tr.td().addText(c.getCode());
1576    addDesignationsToRow(c, designations, tr);
1577    addLangaugesToRow(c, langs, tr);
1578  }
1579
1580
1581  private String describe(FilterOperator op) {
1582    if (op == null)
1583      return " "+ context.formatPhrase(RenderingContext.VALUE_SET_NULL);
1584    switch (op) {
1585    case EQUAL: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_EQUAL);
1586    case ISA: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_ISA);
1587    case ISNOTA: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_ISNOTA);
1588    case REGEX: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_REGEX);
1589    case NULL: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_NULLS);
1590    case IN: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_IN);
1591    case NOTIN: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_NOTIN);
1592    case DESCENDENTOF: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_DESCENDENTOF);
1593    case EXISTS: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_EXISTS);
1594    case GENERALIZES: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_GENERALIZES);
1595    }
1596    return null;
1597  }
1598
1599  private boolean inConcept(String code, ConceptDefinitionComponent c) {
1600    if (c.hasCodeElement() && c.getCode().equals(code))
1601      return true;
1602    for (ConceptDefinitionComponent g : c.getConcept()) {
1603      if (inConcept(code, g))
1604        return true;
1605    }
1606    return false;
1607  }
1608
1609
1610  @Override
1611  protected void genSummaryTableContent(RenderingStatus status, XhtmlNode tbl, CanonicalResource cr) throws IOException {
1612    super.genSummaryTableContent(status, tbl, cr);
1613    
1614    ValueSet vs = (ValueSet) cr;
1615    XhtmlNode tr;
1616
1617    if (CodeSystemUtilities.hasOID(vs)) {
1618      tr = tbl.tr();
1619      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_OID)+":");
1620      tr.td().tx(context.formatPhrase(RenderingContext.CODE_SYS_FOR_OID, CodeSystemUtilities.getOID(vs)));
1621    }
1622  }
1623
1624}