
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}