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