001package org.hl7.fhir.r5.renderers.utils;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.Comparator;
007import java.util.List;
008
009import org.apache.commons.lang3.StringUtils;
010import org.hl7.fhir.exceptions.DefinitionException;
011import org.hl7.fhir.exceptions.FHIRFormatError;
012import org.hl7.fhir.r5.extensions.ExtensionDefinitions;
013import org.hl7.fhir.r5.extensions.ExtensionUtilities;
014import org.hl7.fhir.r5.model.CanonicalResource;
015import org.hl7.fhir.r5.model.CanonicalType;
016import org.hl7.fhir.r5.model.CodeableConcept;
017import org.hl7.fhir.r5.model.Coding;
018import org.hl7.fhir.r5.model.DataType;
019import org.hl7.fhir.r5.model.ElementDefinition;
020import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
021import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
022import org.hl7.fhir.r5.model.Extension;
023import org.hl7.fhir.r5.model.Quantity;
024import org.hl7.fhir.r5.model.Resource;
025import org.hl7.fhir.r5.model.StructureDefinition;
026import org.hl7.fhir.r5.model.ValueSet;
027import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
028import org.hl7.fhir.r5.renderers.DataRenderer;
029import org.hl7.fhir.r5.renderers.Renderer.RenderingStatus;
030import org.hl7.fhir.r5.renderers.utils.ElementTable.HintDrivenGroupingEngine;
031import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
032
033import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
034import org.hl7.fhir.utilities.Utilities;
035import org.hl7.fhir.utilities.json.model.JsonArray;
036import org.hl7.fhir.utilities.json.model.JsonObject;
037import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
038import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
039import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
040import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
041import org.hl7.fhir.utilities.xhtml.NodeType;
042import org.hl7.fhir.utilities.xhtml.XhtmlNode;
043
044
045@MarkedToMoveToAdjunctPackage
046public class ElementTable {
047
048  public static class ElementTableGrouping {
049
050    private long key;
051    private String name;
052    private int priority;
053
054    public ElementTableGrouping(long key, String name, int priority) {
055      this.key = key;
056      this.name = name;
057      this.priority = priority;
058    }
059
060    public String getName() {
061      return name;
062    }
063
064    public int getPriority() {
065      return priority;
066    }
067
068    public long getKey() {
069      return key;
070    }
071
072  }
073  public enum ElementTableGroupingState {
074    UNKNOWN, DEFINES_GROUP, IN_GROUP
075  }
076  public static abstract class ElementTableGroupingEngine {
077
078    public abstract ElementTableGroupingState groupState(ElementDefinition ed);
079    public abstract ElementTableGrouping getGroup(ElementDefinition ed);
080    
081
082    protected boolean nameMatches(String name, List<String> strings) {
083      for (String s : strings) {
084        if (nameMatches(name, s)) {
085          return true;
086        }
087      }
088      return false;
089    }
090    
091    public boolean nameMatches(String name, String test) {
092      if (test.equals(name)) {
093        return true;
094      }
095      if (test.endsWith("[x]") && name.startsWith(test.substring(0, test.length()-3))) {
096        return true;
097      }
098      return false;
099    }    
100  }
101
102  public static class JsonDrivenGroupingEngine extends ElementTableGroupingEngine {
103
104    private JsonArray groups;
105    
106    public JsonDrivenGroupingEngine(JsonArray groups) {
107      super();
108      this.groups = groups;
109    }
110    
111   public ElementTableGroupingState groupState(ElementDefinition ed) {
112     String name = ed.getName();
113      for (JsonObject o : groups.asJsonObjects()  ) {
114        if (nameMatches(name, o.getStrings("elements"))) {
115          return ElementTableGroupingState.IN_GROUP;
116        }
117      }
118      for (JsonObject o : groups.asJsonObjects()  ) {
119        if (o.asBoolean("all")) {
120          return ElementTableGroupingState.IN_GROUP;
121        }
122      }
123      return ElementTableGroupingState.UNKNOWN;
124    }
125    
126    public ElementTableGrouping getGroup(ElementDefinition ed) {
127      String name = ed.getName();
128      int c = 0;
129      for (JsonObject o : groups.asJsonObjects()  ) {
130        c++;
131        if (nameMatches(name, o.getStrings("elements"))) {
132          return new ElementTableGrouping(c, o.asString("name"), groups.size() - c);
133        }
134      }
135      c = 0;
136      for (JsonObject o : groups.asJsonObjects()  ) {
137        c++;
138        if (o.asBoolean("all")) {
139          return new ElementTableGrouping(c, o.asString("name"), groups.size() - c);
140        }
141      }
142      return null;
143    }
144  }
145
146  public static class HintDrivenGroupingEngine extends ElementTableGroupingEngine {
147
148    private List<ElementDefinition> list;
149    
150    public HintDrivenGroupingEngine(List<ElementDefinition> list) {
151      super();
152      this.list = list;
153    }
154    
155    public ElementTableGroupingState groupState(ElementDefinition ed) {
156      if (ed.hasExtension(ExtensionDefinitions.EXT_PROFILE_VIEW_HINT)) {
157        List<Extension> exl = ed.getExtensionsByUrl(ExtensionDefinitions.EXT_PROFILE_VIEW_HINT);
158        for (Extension ex : exl) {
159          if ("element-view-group".equals(ex.getExtensionString("name")) ) {
160            return ElementTableGroupingState.DEFINES_GROUP;
161          }
162        }
163      }
164      return ElementTableGroupingState.UNKNOWN;
165    }
166    
167    public ElementTableGrouping getGroup(ElementDefinition ed) {
168      if (ed.hasExtension(ExtensionDefinitions.EXT_PROFILE_VIEW_HINT)) {
169        String n = null;
170        int order = 0;
171        List<Extension> exl = ed.getExtensionsByUrl(ExtensionDefinitions.EXT_PROFILE_VIEW_HINT);
172        for (Extension ex : exl) {
173          if ("element-view-group".equals(ex.getExtensionString("name")) ) {
174            n = ex.getExtensionString("value");
175          }
176          if ("element-view-order".equals(ex.getExtensionString("name")) ) {
177            order = ExtensionUtilities.readIntegerExtension(ex, "value", 0);
178          }
179        }
180        if (n != null) {
181          return new ElementTableGrouping(ed.getName().hashCode(), n, order);
182        }
183      }
184      return null;
185    }
186  }
187
188  
189  public static enum TableElementDefinitionType {
190    DEFINITION, COMMENT, REQUIREMENTS;
191  } 
192  
193  public static class TableElementDefinition {
194    private TableElementDefinitionType type;
195    private String markdown;
196    public TableElementDefinition(TableElementDefinitionType type, String markdown) {
197      super();
198      this.type = type;
199      this.markdown = markdown;
200    }
201    public TableElementDefinitionType getType() {
202      return type;
203    }
204    public String getMarkdown() {
205      return markdown;
206    }
207    
208  }
209  
210  public enum TableElementConstraintType {
211    CHOICE, PROFILE, TARGET, BINDING, RANGE, FIXED, PATTERN, MAXLENGTH, CARDINALITY;
212  }
213  
214  public static class TableElementConstraint {
215    private TableElementConstraintType type;
216    private DataType value;
217    private DataType value2;
218    private String path;
219    private BindingStrength strength;
220    private String valueSet;
221    private List<TypeRefComponent> types;
222    private List<CanonicalType> list;
223
224    public static TableElementConstraint makeValue(TableElementConstraintType type, String path, DataType value) {
225      TableElementConstraint self = new TableElementConstraint();
226      self.type = type;
227      self.path = path;
228      self.value = value;
229      return self;
230    }
231
232    public static TableElementConstraint makeValueVS(TableElementConstraintType type, String path, DataType value, BindingStrength strength, String valueSet) {
233      TableElementConstraint self = new TableElementConstraint();
234      self.type = type;
235      self.path = path;
236      self.value = value;
237      self.strength = strength;
238      self.valueSet = valueSet;
239      return self;
240    }
241
242    public static TableElementConstraint makeRange(TableElementConstraintType type, String path, DataType value, DataType value2) {
243      TableElementConstraint self = new TableElementConstraint();
244      self.type = type;
245      self.path = path;
246      self.value = value;
247      self.value2 = value2;
248      return self;
249    }
250
251    public static TableElementConstraint makeBinding(TableElementConstraintType type, String path, BindingStrength strength, String valueSet) {
252      TableElementConstraint self = new TableElementConstraint();
253      self.type = type;
254      self.path = path;
255      self.strength = strength;
256      self.valueSet = valueSet;
257      return self;
258    }
259
260    public static TableElementConstraint makeTypes(TableElementConstraintType type, String path, List<TypeRefComponent> types) {
261      TableElementConstraint self = new TableElementConstraint();
262      self.type = type;
263      self.path = path;
264      self.types = types;
265      return self;
266    }
267
268    public static TableElementConstraint makeList(TableElementConstraintType type, String path, List<CanonicalType> list) {
269      TableElementConstraint self = new TableElementConstraint();
270      self.type = type;
271      self.path = path;
272      self.list = list;
273      return self;
274    }
275
276//    private BindingStrength strength;
277//    private ValueSet vs;
278//    private List<String> profiles;
279//    private List<String> targetProfiles;
280//    private DataType fixed;
281//    private DataType pattern;
282//    private DataType minValue;
283//    private DataType maxValue;
284  }
285  
286
287  public class ConstraintsSorter implements Comparator<TableElementConstraint> {
288
289    @Override
290    public int compare(TableElementConstraint o1, TableElementConstraint o2) {
291      int r = StringUtils.compare(o1.path, o2.path);
292      return r == 0 ? o1.type.compareTo(o2.type) : r;
293    }
294  }
295  
296  public static class TableElementInvariant {
297    private String level;
298    private String human;
299    private String fhirPath;
300    private String other;
301    private String otherFormat;
302  }
303  
304  public static class TableElement {
305    // column 1
306    private String path;
307    private String name;
308    private String min;
309    private String max;
310    private String typeName;
311    private String typeIcon;
312    private String typeLink;
313    private String typeHint;
314    
315    // column 2
316    private List<TableElementDefinition> definitions = new ArrayList<>();
317    
318    // column 3
319    private List<TableElementConstraint> constraints = new ArrayList<>();
320    
321    private List<TableElementInvariant> invariants = new ArrayList<>();
322    
323    private List<TableElement> childElements = new ArrayList<ElementTable.TableElement>();
324
325    public TableElement(String path, String name, String min, String max) {
326      super();
327      this.path = path;
328      this.name = name;
329      this.min = min;
330      this.max = max;
331    }
332
333    public String getPath() {
334      return path;
335    }
336
337    public String getName() {
338      return name;
339    }
340
341    public String getMin() {
342      return min;
343    }
344
345    public String getMax() {
346      return max;
347    }
348    public String getTypeName() {
349      return typeName;
350    }
351    public String getTypeIcon() {
352      return typeIcon;
353    }
354    public String getTypeLink() {
355      return typeLink;
356    }
357    public String getTypeHint() {
358      return typeHint;
359    }
360
361    public List<TableElementDefinition> getDefinitions() {
362      return definitions;
363    }
364
365    public List<TableElementConstraint> getConstraints() {
366      return constraints;
367    }
368
369    public List<TableElementInvariant> getInvariants() {
370      return invariants;
371    }
372
373    public List<TableElement> getChildElements() {
374      return childElements;
375    }
376
377    public TableElement setPath(String path) {
378      this.path = path;
379      return this;
380    }
381
382    public TableElement setName(String name) {
383      this.name = name;
384      return this;
385    }
386
387    public TableElement setMin(String min) {
388      this.min = min;
389      return this;
390    }
391
392    public TableElement setMax(String max) {
393      this.max = max;
394      return this;
395    }
396    
397    public void setType(String name, String link, String hint, String icon) {
398      this.typeName = name;
399      this.typeIcon = icon;
400      this.typeLink = link;
401      this.typeHint = hint;
402    }
403    
404  }
405  
406  public static class TableGroup {
407    private String name;
408    private String documentation;
409    private boolean buildIfEmpty;
410    private String emptyNote;
411    private List<TableElement> elements = new ArrayList<ElementTable.TableElement>();
412    private int priorty;
413    private int counter;
414    private ElementTableGrouping definition;
415    
416    public TableGroup(int counter, ElementTableGrouping definition) {
417      super();
418      this.counter = counter;
419      this.definition = definition;
420      name = definition.getName();
421      priorty = definition.getPriority();
422      buildIfEmpty = false;            
423    }
424
425    public String getName() {
426      return name;
427    }
428
429    public String getDocumentation() {
430      return documentation;
431    }
432
433    public boolean isBuildIfEmpty() {
434      return buildIfEmpty;
435    }
436
437    public String getEmptyNote() {
438      return emptyNote;
439    }
440
441    public List<TableElement> getElements() {
442      return elements;
443    }
444
445    public int getPriorty() {
446      return priorty;
447    }
448
449    public int getCounter() {
450      return counter;
451    }
452
453    public ElementTableGrouping getDefinition() {
454      return definition;
455    }
456    
457    
458  }
459  
460  public static class TableGroupSorter implements Comparator<TableGroup> {
461
462    @Override
463    public int compare(TableGroup o1, TableGroup o2) {
464      if (o1.priorty == o2.priorty) {
465        return Integer.compare(o1.counter, o2.counter);        
466      } else {
467        return Integer.compare(o2.priorty, o1.priorty); // priorty sorts backwards
468      }
469    }
470  }
471  
472  private RenderingContext context;
473  private List<TableGroup> groups;
474  private DataRenderer dr;
475  private boolean replaceCardinality;
476  
477  public ElementTable(RenderingContext context, List<TableGroup> groups, DataRenderer dr, boolean replaceCardinality) {
478    this.context = context;
479    this.groups = groups;
480    this.dr = dr;
481    this.replaceCardinality = replaceCardinality;
482  }
483
484  public void build(HierarchicalTableGenerator gen, TableModel table) throws FHIRFormatError, DefinitionException, IOException {
485    Collections.sort(groups, new TableGroupSorter());
486    table.setBorder(true);
487    table.setShowHeadings(false);
488
489    
490    for (TableGroup grp : groups) {
491      if (grp.getElements().size() > 0 || grp.buildIfEmpty) {
492        renderGroup(gen, table, grp);
493      }
494    }
495    
496  }
497
498  private void renderGroup(HierarchicalTableGenerator gen, TableModel table, TableGroup grp) throws FHIRFormatError, DefinitionException, IOException {
499    Row row = gen.new Row();
500    table.getRows().add(row);
501    Cell cell = gen.new Cell(null, null, grp.getName(), null, null);
502    row.getCells().add(cell);
503    cell.span(3);
504    row.setColor("#dfdfdf");
505    cell.addStyle("vertical-align: middle");
506    cell.addStyle("font-weight: bold");
507    cell.addStyle("font-size: 14px");
508    cell.addStyle("padding-top: 10px");
509    cell.addStyle("padding-bottom: 10px");
510
511    boolean first = true;
512    for (TableElement e : grp.elements) {
513      renderElement(gen, row.getSubRows(), e, first);
514      first = false;
515    }
516  }
517
518  private void renderElement(HierarchicalTableGenerator gen, List<Row> rows, TableElement e, boolean first) throws FHIRFormatError, DefinitionException, IOException {
519    Row row = gen.new Row();
520    rows.add(row);
521    if (!first) {
522      row.setTopLine("silver");
523    }
524    renderElementIdentity(gen, row, e);
525    renderElementDefinition(gen, row, e);
526    renderElementConstraints(gen, row, e);
527
528    
529//    if (e.invariants.size() > 0) {
530//      tr.style("border-bottom: none");
531//      tr = table.tr();
532//      tr.style("border-top: none");
533//      tr.style("border-left: black 1px solid");
534//      tr.style("border-right: black 1px solid");  
535//      renderElementInvariants(tr.td().colspan(3), e);
536//    } 
537//    tr.style("border-bottom: silver 1px solid");
538    
539    for (TableElement child : e.getChildElements()) {
540      renderElement(gen, row.getSubRows(), child, false);
541    }
542  }
543
544  public void renderElementIdentity(HierarchicalTableGenerator gen, Row row, TableElement e) {
545    Cell cell = gen.new Cell();
546    cell.addCellStyle("min-width: 220px");
547    row.getCells().add(cell);
548    cell.setInnerTable(true);
549    cell.addText(e.getName()).addStyle("font-weight: bold");
550    cell.addPiece(gen.new Piece("br"));
551    if (!replaceCardinality) {
552      cell.addText("Cardinality: "+e.min+".."+e.max);  
553    } else if ("1".equals(e.min) && "1".equals(e.max)) {
554      cell.addText("Required");
555    } else if ("0".equals(e.min) && "*".equals(e.max)) {
556      cell.addText("Optional, Repeating");
557    } else if ("0".equals(e.min) && "1".equals(e.max)) {
558      cell.addText("Optional");
559    } else if ("1".equals(e.min) && "*".equals(e.max)) {
560      cell.addText("Repeating");
561    } else {
562      cell.addText("Cardinality: "+e.min+".."+e.max);      
563    }
564    cell.addPiece(gen.new Piece("br"));
565    cell.addImg(e.getTypeIcon(), e.getTypeHint(), e.getTypeLink());
566    cell.addPiece(gen.new Piece(e.getTypeLink(), " "+e.getTypeName(), e.getTypeHint()));      
567  }
568
569  public void renderElementConstraints(HierarchicalTableGenerator gen, Row row, TableElement e) throws FHIRFormatError, DefinitionException, IOException {
570    Cell cell = gen.new Cell();
571    cell.addCellStyle("min-width: 300px");
572    row.getCells().add(cell);
573    XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
574    
575    Collections.sort(e.getConstraints(), new ConstraintsSorter());
576    boolean first = true;
577    for (TableElementConstraint c : e.getConstraints()) {
578      if (first) first= false; else div.br();
579      switch (c.type) {
580      case BINDING:
581        renderBindingConstraint(div, c);
582        break;
583      case CHOICE:
584        renderChoiceConstraint(div, c);
585        break;
586      case FIXED:
587        renderValueConstraint(div, c);
588        break;
589      case MAXLENGTH:
590        renderValueConstraint(div, c);
591        break;
592      case PATTERN:
593        renderValueConstraint(div, c);
594        break;
595      case PROFILE:
596        renderListConstraint(div, c);
597        break;
598      case RANGE:
599        renderRangeConstraint(div, c);
600        break;
601      case TARGET:
602        renderListConstraint(div, c);
603        break;
604      case CARDINALITY:
605        renderCardinalityConstraint(div, c);
606      break;
607      default:
608        break;
609      
610      }
611    }
612    cell.addXhtml(div);
613  }
614
615  private void renderBindingConstraint(XhtmlNode x, TableElementConstraint c) {
616    String name = c.path == null ? "value" : c.path;
617    x.code().tx(name);
618    renderBinding(x, c, " is bound to "); 
619  }
620
621  private void renderBinding(XhtmlNode x, TableElementConstraint c, String phrase) {
622    ValueSet vs = context.getContext().findTxResource(ValueSet.class, c.valueSet);
623    if (vs == null) {
624      x.tx(phrase+"an unknown valueset ");
625      x.code().tx(c.valueSet);      
626    } else {
627      x.tx(phrase);
628      x.ah(vs.getWebPath()).tx(vs.present());
629      try {      
630        ValueSetExpansionOutcome exp = context.getContext().expandVS(vs, true, false);
631        if (!exp.isOk()) {
632          x.span().attribute("title", exp.getError()).tx(" (??)");                  
633        } else if (exp.getValueset().getExpansion().getContains().size() == 1000) {
634          x.tx(" (>1000 codes)");                            
635        } else if (exp.getValueset().getExpansion().getContains().size() > 6) {
636          x.tx(" ("+exp.getValueset().getExpansion().getContains().size()+" codes)");                            
637        } else {
638          x.tx(".  Codes:");
639          XhtmlNode ul = x.ul();
640          for (ValueSetExpansionContainsComponent cc : exp.getValueset().getExpansion().getContains()) {
641
642            String url = cc.hasSystem() && cc.hasCode() ? dr.getLinkForCode(cc.getSystem(), cc.getVersion(), cc.getCode()) : null; 
643            var li = ul.li();
644            li.ahOrNot(url).tx(dr.displayCodeSource(cc.getSystem(), cc.getVersion())+": "+cc.getCode());
645            if (cc.hasDisplay()) {
646              li.tx(" \""+cc.getDisplay()+"\"");
647            } 
648          }
649        }
650      } catch (Exception e) {
651        x.span().attribute("title", e.getMessage()).tx(" (??)");        
652      }
653    }
654  }
655
656  private void renderChoiceConstraint(XhtmlNode x, TableElementConstraint c) {
657    String name = c.path == null ? "value" : c.path;
658    x.code().tx(name);
659    x.tx(" is a choice of:");
660    var ul = x.ul();
661    for (TypeRefComponent tr : c.types) {
662      if (tr.hasProfile()) {
663        for (CanonicalType ct : tr.getProfile()) {
664          StructureDefinition sd = context.getContext().fetchTypeDefinition(ct.primitiveValue());
665          if (sd == null || !sd.hasWebPath()) {
666            ul.li().ah(ct.primitiveValue()).tx(ct.primitiveValue());
667          } else {
668            ul.li().ah(sd.getWebPath()).tx(sd.present());
669          }
670        }
671      } else if (tr.hasTarget()) {
672        StructureDefinition sd = context.getContext().fetchTypeDefinition(tr.getWorkingCode());
673        var li = ul.li();
674        li.ah(sd.getWebPath()).tx(sd.present());
675        li.tx(" pointing to ");
676        renderTypeList(x, tr.getTargetProfile());        
677        
678      } else {
679        StructureDefinition sd = context.getContext().fetchTypeDefinition(tr.getWorkingCode());
680        if (sd == null || !sd.hasWebPath()) {
681          ul.li().code().tx(tr.getWorkingCode());
682        } else {
683          ul.li().ah(sd.getWebPath()).tx(sd.present());
684        }
685      }
686    }
687    
688    
689  }
690
691  private void renderValueConstraint(XhtmlNode x, TableElementConstraint c) throws FHIRFormatError, DefinitionException, IOException {
692    String name = c.path == null ? "value" : c.path;
693    x.code().tx(name);
694    switch (c.type) {
695    case FIXED:
696      x.tx(" is fixed to ");
697      break;
698    case MAXLENGTH:
699      x.tx(" is limited  in length to ");
700
701      break;
702    case PATTERN:
703      if (c.value.isPrimitive()) {
704        x.tx(" is fixed to ");
705      } else {
706        x.tx(" must match ");
707      }
708      break;
709    default:
710      break;
711    }
712    renderValue(x, c.value);
713    if (c.strength != null && c.valueSet != null) {
714      renderBinding(x, c, " from ");
715    }
716  }
717
718  public void renderValue(XhtmlNode x, DataType v) throws IOException {
719    if (v.isPrimitive()) {
720      String s = v.primitiveValue();
721      if (Utilities.isAbsoluteUrl(s)) {
722        Resource res = context.getContext().fetchResource(Resource.class, s);
723        if (res != null && res.hasWebPath()) {
724          x.ah(res.getWebPath()).tx(res instanceof CanonicalResource ? ((CanonicalResource) res).present() : s);                    
725        } else if (Utilities.isAbsoluteUrlLinkable(s)) {
726          x.ah(s).code().tx(s);          
727        } else {
728          x.code().tx(s);
729        }
730      } else {
731        x.code().tx(s);
732      }
733    } else if (v instanceof Quantity) {
734      genQuantity(x, (Quantity) v);        
735    } else if (v instanceof Coding) {
736      genCoding(x, (Coding) v);
737    } else if (v instanceof CodeableConcept) {
738      genCodeableConcept(x, (CodeableConcept) v);
739    } else {
740      dr.renderBase(new RenderingStatus(), x, v);
741    }
742  }
743
744  private void genCodeableConcept(XhtmlNode div, CodeableConcept cc) {
745    boolean first = true;
746    for (Coding c : cc.getCoding()) {
747      if (first) first = false; else div.tx(",");
748      genCoding(div, c);
749    }
750    if (cc.hasText()) {
751      div.code().tx(" \""+cc.getText()+"\"");      
752    }
753    
754  }
755
756  public void genQuantity(XhtmlNode div, Quantity q) {
757    String url = q.hasSystem() && q.hasUnit() ? dr.getLinkForCode(q.getSystem(), null, q.getCode()) : null; 
758    var code = div.code();
759    if (q.hasComparator()) {
760      code.tx(q.getComparator().toCode());
761    }
762    code.tx(q.getValueElement().asStringValue());
763    code.ahOrNot(url).tx(q.getUnit());
764  }
765
766  public void genCoding(XhtmlNode div, Coding c) {
767    String url = c.hasSystem() && c.hasCode() ? dr.getLinkForCode(c.getSystem(), c.getVersion(), c.getCode()) : null; 
768    var code = div.code();
769    code.ahOrNot(url).tx(dr.displayCodeSource(c.getSystem(), c.getVersion())+": "+c.getCode());
770    if (c.hasDisplay()) {
771      code.tx(" \""+c.getDisplay()+"\"");
772    } else {
773      String s = dr.lookupCode(c.getSystem(), c.getVersion(), c.getCode());
774      if (s != null) {
775        div.span().style("opacity: 0.5").tx("(\""+s+"\")");
776      }
777    }
778  }
779
780  private void renderRangeConstraint(XhtmlNode x, TableElementConstraint c) throws IOException {
781    String name = c.path == null ? "value" : c.path;
782    if (c.value != null && c.value2 != null) {
783      x.tx(name + " between ");
784      renderValue(x, c.value);
785      x.tx(" and " );
786      renderValue(x, c.value2);
787    } else if (c.value != null) {
788      x.tx(name + " more than ");
789      renderValue(x, c.value);
790    } else  {
791      x.tx(name + " less than ");
792      renderValue(x, c.value2);
793    }
794  }
795
796  private void renderCardinalityConstraint(XhtmlNode x, TableElementConstraint c) throws IOException {
797    String name = c.path == null ? "value" : c.path;
798    x.code().tx(name);
799    String min = c.value.primitiveValue();
800    String max = c.value2.primitiveValue(); 
801    if (!replaceCardinality) {
802      x.tx("has cardinality: "+min+".."+max);  
803    } else if ("1".equals(min) && "1".equals(max)) {
804      x.tx("is required");
805    } else if ("0".equals(min) && "*".equals(max)) {
806      x.tx("is Optional and repeats");
807    } else if ("0".equals(min) && "1".equals(max)) {
808      x.tx("is Optional");
809    } else if ("1".equals(min) && "*".equals(max)) {
810      x.tx("repeats");
811    } else {
812      x.tx("has cardinality: "+min+".."+max);      
813    }
814  }
815
816  private void renderListConstraint(XhtmlNode x, TableElementConstraint c) {
817    String name = c.path == null ? "value" : c.path;
818
819    x.code().tx(name);
820    switch (c.type) {
821    case PROFILE:
822      x.tx(" must be ");
823      break;
824    case TARGET:
825      x.tx(" must point to ");
826      break;
827    default:
828      break;
829    }
830    renderTypeList(x, c.list);
831  }
832
833  private void renderTypeList(XhtmlNode x, List<CanonicalType> list) {
834
835    if (list.size() == 1) {
836      x.tx("a ");
837    } else {
838      x.tx("one of ");
839    }
840    boolean first = true;
841    for (int i = 0; i < list.size(); i++) {
842      if (first) {
843        first = false;
844      } else if (i == list.size() - 1) {
845        x.tx(" or " );
846      } else {
847        x.tx(", ");
848      }
849      String s = list.get(i).primitiveValue();
850      Resource res = context.getContext().fetchResource(Resource.class, s);
851      if (res != null && res.hasWebPath()) {
852        x.ah(res.getWebPath()).tx(res instanceof CanonicalResource ? ((CanonicalResource) res).present() : s);
853      } else {
854        x.ah(s).tx(s);
855      }
856    }    
857  }
858
859  public void renderElementDefinition(HierarchicalTableGenerator gen, Row row, TableElement e) {
860    Cell cell = gen.new Cell();    row.getCells().add(cell);
861    for (TableElementDefinition d : e.definitions) {
862      if (d.getType() == TableElementDefinitionType.DEFINITION) {
863        cell.addMarkdown(d.getMarkdown());
864      } else if (d.getType() == TableElementDefinitionType.COMMENT) {
865        cell.addMarkdown("Comment: "+d.getMarkdown(), "font-style: italic");
866      }
867    }
868  }
869
870  private void renderElementInvariants(XhtmlNode td, TableElement e) {
871    XhtmlNode ul = td.ul();
872    for (TableElementInvariant t : e.invariants) {
873      var li = ul.li();
874      li.tx(t.level+": "+t.human);
875      li.tx(" ");
876      li.code().tx(t.other != null ? t.other : t.fhirPath);
877    }
878  
879  }
880
881}