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