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