
001package org.hl7.fhir.r5.renderers; 002 003import java.io.ByteArrayOutputStream; 004import java.io.IOException; 005import java.io.UnsupportedEncodingException; 006import java.util.ArrayList; 007import java.util.HashMap; 008import java.util.HashSet; 009import java.util.List; 010import java.util.Stack; 011import java.util.Map; 012import java.util.Set; 013 014import org.apache.commons.lang3.StringUtils; 015import org.hl7.fhir.exceptions.DefinitionException; 016import org.hl7.fhir.exceptions.FHIRException; 017import org.hl7.fhir.exceptions.FHIRFormatError; 018import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation; 019import org.hl7.fhir.r5.conformance.profile.BindingResolution; 020import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 021import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ElementChoiceGroup; 022import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ExtensionContext; 023import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; 024import org.hl7.fhir.r5.formats.IParser; 025import org.hl7.fhir.r5.formats.IParser.OutputStyle; 026import org.hl7.fhir.r5.formats.JsonParser; 027import org.hl7.fhir.r5.formats.XmlParser; 028import org.hl7.fhir.r5.model.ActorDefinition; 029import org.hl7.fhir.r5.model.Base; 030import org.hl7.fhir.r5.model.BooleanType; 031import org.hl7.fhir.r5.model.CanonicalResource; 032import org.hl7.fhir.r5.model.CanonicalType; 033import org.hl7.fhir.r5.model.CodeSystem; 034import org.hl7.fhir.r5.model.CodeType; 035import org.hl7.fhir.r5.model.CodeableConcept; 036import org.hl7.fhir.r5.model.Coding; 037import org.hl7.fhir.r5.model.DataType; 038import org.hl7.fhir.r5.model.Element; 039import org.hl7.fhir.r5.model.ElementDefinition; 040import org.hl7.fhir.r5.model.ElementDefinition.AdditionalBindingPurposeVS; 041import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode; 042import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; 043import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent; 044import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 045import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; 046import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent; 047import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; 048import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; 049import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 050import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation; 051import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; 052import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 053import org.hl7.fhir.r5.model.Enumeration; 054import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 055import org.hl7.fhir.r5.model.Extension; 056import org.hl7.fhir.r5.model.IdType; 057import org.hl7.fhir.r5.model.IntegerType; 058import org.hl7.fhir.r5.model.MarkdownType; 059import org.hl7.fhir.r5.model.PrimitiveType; 060import org.hl7.fhir.r5.model.Quantity; 061import org.hl7.fhir.r5.model.Resource; 062import org.hl7.fhir.r5.model.StringType; 063import org.hl7.fhir.r5.model.StructureDefinition; 064import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 065import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent; 066import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 067import org.hl7.fhir.r5.model.UriType; 068import org.hl7.fhir.r5.model.ValueSet; 069import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; 070import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer.InternalMarkdownProcessor; 071import org.hl7.fhir.r5.renderers.utils.RenderingContext; 072import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 073import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType; 074import org.hl7.fhir.r5.renderers.utils.RenderingContext.StructureDefinitionRendererMode; 075import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; 076import org.hl7.fhir.r5.utils.PublicationHacker; 077import org.hl7.fhir.r5.utils.ToolingExtensions; 078import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 079import org.hl7.fhir.utilities.MarkDownProcessor; 080import org.hl7.fhir.utilities.StandardsStatus; 081import org.hl7.fhir.utilities.Utilities; 082import org.hl7.fhir.utilities.VersionUtilities; 083import org.hl7.fhir.utilities.i18n.I18nConstants; 084import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 085import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 086import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 087import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 088import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 089import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMode; 090import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 091import org.hl7.fhir.utilities.xhtml.NodeType; 092import org.hl7.fhir.utilities.xhtml.XhtmlNode; 093import org.hl7.fhir.utilities.xhtml.XhtmlParser; 094 095public class StructureDefinitionRenderer extends ResourceRenderer { 096 097 // public class ObligationWrapper { 098 // 099 // private Extension ext; 100 // 101 // public ObligationWrapper(Extension ext) { 102 // this.ext = ext; 103 // } 104 // 105 // public boolean hasActor() { 106 // return ext.hasExtension("actor"); 107 // } 108 // 109 // public boolean hasActor(String id) { 110 // return ext.hasExtension("actor") && id.equals(ext.getExtensionByUrl("actor").getValue().primitiveValue()); 111 // } 112 // 113 // public Coding getCode() { 114 // Extension code = ext.getExtensionByUrl("obligation"); 115 // if (code != null && code.hasValueCoding()) { 116 // return code.getValueCoding(); 117 // } 118 // if (code != null && code.hasValueCodeType()) { 119 // return new Coding().setSystem("http://hl7.org/fhir/tools/CodeSystem/obligation").setCode(code.getValueCodeType().primitiveValue()); 120 // } 121 // return null; 122 // } 123 // 124 // public boolean hasFilter() { 125 // return ext.hasExtension("filter"); 126 // } 127 // 128 // public String getFilter() { 129 // Extension code = ext.getExtensionByUrl("filter"); 130 // if (code != null && code.getValue() != null) { 131 // return code.getValue().primitiveValue(); 132 // } 133 // return null; 134 // } 135 // 136 // public boolean hasUsage() { 137 // return ext.hasExtension("usage"); 138 // } 139 // 140 // public String getFilterDocumentation() { 141 // Extension code = ext.getExtensionByUrl("filter-desc"); 142 // if (code != null && code.getValue() != null) { 143 // return code.getValue().primitiveValue(); 144 // } 145 // return null; 146 // } 147 // 148 // public List<UsageContext> getUsage() { 149 // List<UsageContext> usage = new ArrayList<>(); 150 // for (Extension u : ext.getExtensionsByUrl("usage" )) { 151 // if (u.hasValueUsageContext()) { 152 // usage.add(u.getValueUsageContext()); 153 // } 154 // } 155 // return usage; 156 // } 157 // 158 // } 159 160 public class InternalMarkdownProcessor implements IMarkdownProcessor { 161 162 @Override 163 public String processMarkdown(String location, PrimitiveType md) throws FHIRException { 164 return context.getMarkdown().process(md.primitiveValue(), location); 165 } 166 167 @Override 168 public String processMarkdown(String location, String text) throws FHIRException { 169 return context.getMarkdown().process(text, location); 170 } 171 } 172 173 private enum ListItemStatus { New, Unchanged, Removed}; 174 175 private abstract class ItemWithStatus { 176 ListItemStatus status = ListItemStatus.New; // new, unchanged, removed 177 178 protected abstract void renderDetails(XhtmlNode f) throws IOException; 179 protected abstract boolean matches(ItemWithStatus other); 180 181 public void render(XhtmlNode x) throws IOException { 182 XhtmlNode f = x; 183 if (status == ListItemStatus.Unchanged) { 184 f = unchanged(f); 185 } else if (status == ListItemStatus.Removed) { 186 f = removed(f); 187 } 188 renderDetails(f); 189 } 190 } 191 192 protected class StatusList<T extends ItemWithStatus> extends ArrayList<T> implements List<T> { 193 194 public boolean merge(T item) { 195 if (item == null) { 196 return false; 197 } 198 boolean found = false; 199 for (T t : this) { 200 if (t.matches(item)) { 201 found = true; 202 t.status = ListItemStatus.Unchanged; 203 } 204 } 205 if (!found) { 206 item.status = ListItemStatus.Removed; 207 return add(item); 208 } else { 209 return false; 210 } 211 } 212 213 public boolean add(T item) { 214 if (item != null) { 215 return super.add(item); 216 } else { 217 return false; 218 } 219 } 220 } 221 222 private class ResolvedCanonical extends ItemWithStatus { 223 String url; // what we used to resolve 224 CanonicalResource cr; // what we resolved 225 226 public ResolvedCanonical(String url, CanonicalResource cr) { 227 this.url = url; 228 this.cr = cr; 229 } 230 public void renderDetails(XhtmlNode f) { 231 if (cr != null && cr.hasWebPath()) { 232 f.ah(cr.getWebPath()).tx(cr.present()); 233 } else { 234 f.code().tx(url); 235 } 236 } 237 protected boolean matches(ItemWithStatus other) { 238 return ((ResolvedCanonical) other).url.equals(url); 239 } 240 } 241 242 private class InvariantWithStatus extends ItemWithStatus { 243 ElementDefinitionConstraintComponent value; 244 protected InvariantWithStatus(ElementDefinitionConstraintComponent value) { 245 this.value = value; 246 } 247 248 protected boolean matches(ItemWithStatus other) { 249 return ((InvariantWithStatus) other).value.equalsDeep(value); 250 } 251 252 public void renderDetails(XhtmlNode f) { 253 f = renderStatus(value, f); 254 f.b().attribute("title", "Formal Invariant Identifier").tx(value.getKey()); 255 f.tx(": "); 256 if (value.hasHuman()) { 257 renderStatus(value.getHumanElement(), f).tx(value.getHuman()); 258 } else if (VersionComparisonAnnotation.hasDeleted(value, "human")) { 259 Base b =VersionComparisonAnnotation.getDeletedItem(value, "human"); 260 renderStatus(b, f).tx(b.primitiveValue()); 261 } 262 f.tx(" ("); 263 if (status == ListItemStatus.New) { 264 if (value.hasExpression()) { 265 renderStatus(value.getExpressionElement(), f).code().tx(value.getExpression()); 266 } else if (VersionComparisonAnnotation.hasDeleted(value, "expression")) { 267 Base b = VersionComparisonAnnotation.getDeletedItem(value, "expression"); 268 renderStatus(b, f).code().tx(b.primitiveValue()); 269 } 270 } else { 271 renderStatus(value.getExpressionElement(), f).tx(value.getExpression()); 272 } 273 f.tx(")"); 274 } 275 } 276 277 private class DiscriminatorWithStatus extends ItemWithStatus { 278 ElementDefinitionSlicingDiscriminatorComponent value; 279 protected DiscriminatorWithStatus(ElementDefinitionSlicingDiscriminatorComponent value) { 280 this.value = value; 281 } 282 283 protected boolean matches(ItemWithStatus other) { 284 return ((DiscriminatorWithStatus) other).value.equalsDeep(value); 285 } 286 287 public void renderDetails(XhtmlNode f) { 288 f.tx(value.getType().toCode()); 289 f.tx(" @ "); 290 f.tx(value.getPath()); 291 } 292 } 293 294 private class ValueWithStatus extends ItemWithStatus { 295 PrimitiveType value; 296 protected ValueWithStatus(PrimitiveType value) { 297 this.value = value; 298 } 299 300 protected boolean matches(ItemWithStatus other) { 301 return ((ValueWithStatus) other).value.equalsDeep(value); 302 } 303 304 public void renderDetails(XhtmlNode f) { 305 if (value.hasUserData("render.link")) { 306 f = f.ah(value.getUserString("render.link")); 307 } 308 f.tx(value.asStringValue()); 309 } 310 311 } 312 313 private class DataValueWithStatus extends ItemWithStatus { 314 DataType value; 315 protected DataValueWithStatus(DataType value) { 316 this.value = value; 317 } 318 319 protected boolean matches(ItemWithStatus other) { 320 return ((ValueWithStatus) other).value.equalsDeep(value); 321 } 322 323 public void renderDetails(XhtmlNode f) throws IOException { 324 325 if (value.hasUserData("render.link")) { 326 f = f.ah(value.getUserString("render.link")); 327 } 328 f.tx(summarize(value)); 329 } 330 331 } 332 333 334 private List<String> keyRows = new ArrayList<>(); 335 private Map<String, Map<String, ElementDefinition>> sdMapCache = new HashMap<>(); 336 private IMarkdownProcessor hostMd; 337 338 public StructureDefinitionRenderer(RenderingContext context) { 339 super(context); 340 hostMd = new InternalMarkdownProcessor(); 341 corePath = context.getContext().getSpecUrl(); 342 } 343 344 public StructureDefinitionRenderer(RenderingContext context, ResourceContext rcontext) { 345 super(context, rcontext); 346 } 347 348 349 public Map<String, Map<String, ElementDefinition>> getSdMapCache() { 350 return sdMapCache; 351 } 352 353 public void setSdMapCache(Map<String, Map<String, ElementDefinition>> sdMapCache) { 354 this.sdMapCache = sdMapCache; 355 } 356 357 public IMarkdownProcessor getHostMd() { 358 return hostMd; 359 } 360 361 public void setHostMd(IMarkdownProcessor hostMd) { 362 this.hostMd = hostMd; 363 } 364 365 public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { 366 return render(x, (StructureDefinition) dr); 367 } 368 369 public boolean render(XhtmlNode x, StructureDefinition sd) throws FHIRFormatError, DefinitionException, IOException { 370 if (context.getStructureMode() == StructureDefinitionRendererMode.DATA_DICT) { 371 renderDict(sd, sd.getDifferential().getElement(), x.table("dict"), false, GEN_MODE_DIFF, ""); 372 } else { 373 x.getChildNodes().add(generateTable(context.getDefinitionsTarget(), sd, true, context.getDestDir(), false, sd.getId(), false, 374 context.getLink(KnownLinkType.SPEC), "", sd.getKind() == StructureDefinitionKind.LOGICAL, false, null, false, context, "")); 375 } 376 return true; 377 } 378 379 public void describe(XhtmlNode x, StructureDefinition sd) { 380 x.tx(display(sd)); 381 } 382 383 public String display(StructureDefinition sd) { 384 return sd.present(); 385 } 386 387 @Override 388 public String display(Resource r) throws UnsupportedEncodingException, IOException { 389 return ((StructureDefinition) r).present(); 390 } 391 392 public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 393 if (r.has("title")) { 394 return r.children("title").get(0).getBase().primitiveValue(); 395 } 396 if (r.has("name")) { 397 return r.children("name").get(0).getBase().primitiveValue(); 398 } 399 return "??"; 400 } 401 402 403 // private static final int AGG_NONE = 0; 404 // private static final int AGG_IND = 1; 405 // private static final int AGG_GR = 2; 406 // private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false; 407 public static final String CONSTRAINT_CHAR = "C"; 408 public static final String CONSTRAINT_STYLE = "padding-left: 3px; padding-right: 3px; border: 1px maroon solid; font-weight: bold; color: #301212; background-color: #fdf4f4;"; 409 public static final int GEN_MODE_SNAP = 1; 410 public static final int GEN_MODE_DIFF = 2; 411 public static final int GEN_MODE_MS = 3; 412 public static final int GEN_MODE_KEY = 4; 413 public static final String RIM_MAPPING = "http://hl7.org/v3"; 414 public static final String v2_MAPPING = "http://hl7.org/v2"; 415 public static final String LOINC_MAPPING = "http://loinc.org"; 416 public static final String SNOMED_MAPPING = "http://snomed.info"; 417 418 private final boolean ADD_REFERENCE_TO_TABLE = true; 419 420 private boolean useTableForFixedValues = true; 421 private String corePath; 422 423 public static class UnusedTracker { 424 private boolean used; 425 } 426 427 private class SpanEntry { 428 private List<SpanEntry> children = new ArrayList<SpanEntry>(); 429 private boolean profile; 430 private String id; 431 private String name; 432 private String resType; 433 private String cardinality; 434 private String description; 435 private String profileLink; 436 private String resLink; 437 private String type; 438 439 public String getName() { 440 return name; 441 } 442 public void setName(String name) { 443 this.name = name; 444 } 445 public String getResType() { 446 return resType; 447 } 448 public void setResType(String resType) { 449 this.resType = resType; 450 } 451 public String getCardinality() { 452 return cardinality; 453 } 454 public void setCardinality(String cardinality) { 455 this.cardinality = cardinality; 456 } 457 public String getDescription() { 458 return description; 459 } 460 public void setDescription(String description) { 461 this.description = description; 462 } 463 public String getProfileLink() { 464 return profileLink; 465 } 466 public void setProfileLink(String profileLink) { 467 this.profileLink = profileLink; 468 } 469 public String getResLink() { 470 return resLink; 471 } 472 public void setResLink(String resLink) { 473 this.resLink = resLink; 474 } 475 public String getId() { 476 return id; 477 } 478 public void setId(String id) { 479 this.id = id; 480 } 481 public boolean isProfile() { 482 return profile; 483 } 484 public void setProfile(boolean profile) { 485 this.profile = profile; 486 } 487 public List<SpanEntry> getChildren() { 488 return children; 489 } 490 public String getType() { 491 return type; 492 } 493 public void setType(String type) { 494 this.type = type; 495 } 496 497 } 498 499 private class ElementInStructure { 500 501 private StructureDefinition source; 502 private ElementDefinition element; 503 504 public ElementInStructure(StructureDefinition source, ElementDefinition ed) { 505 this.source = source; 506 this.element = ed; 507 } 508 509 public StructureDefinition getSource() { 510 return source; 511 } 512 513 public ElementDefinition getElement() { 514 return element; 515 } 516 517 } 518 private ElementInStructure getElementByName(List<ElementDefinition> elements, String contentReference, StructureDefinition source) { 519 if (contentReference.contains("#")) { 520 String url = contentReference.substring(0, contentReference.indexOf("#")); 521 contentReference = contentReference.substring(contentReference.indexOf("#")); 522 if (Utilities.noString(url)) { 523 url = source.getUrl(); 524 } 525 if (!url.equals(source.getUrl())) { 526 source = context.getWorker().fetchResource(StructureDefinition.class, url, source); 527 if (source == null) { 528 throw new FHIRException("Unable to resolve StructureDefinition "+url+" resolving content reference "+contentReference); 529 } 530 elements = source.getSnapshot().getElement(); 531 } 532 } 533 for (ElementDefinition ed : elements) { 534 if (("#"+ed.getPath()).equals(contentReference)) { 535 return new ElementInStructure(source, ed); 536 } 537 if (("#"+ed.getId()).equals(contentReference)) { 538 return new ElementInStructure(source, ed); 539 } 540 } 541 throw new Error("getElementByName: can't find "+contentReference+" in "+elements.toString()+" from "+source.getUrl()); 542 // return null; 543 } 544 545 public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException { 546 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 547 gen.setTranslator(getTranslator()); 548 TableModel model = gen.initGridTable(corePath, profile.getId()); 549 List<ElementDefinition> list = profile.getSnapshot().getElement(); 550 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 551 profiles.add(profile); 552 genGridElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, true, profileBaseFileName, null, corePath, imagePath, true, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list)); 553 try { 554 return gen.generate(model, imagePath, 1, outputTracker); 555 } catch (org.hl7.fhir.exceptions.FHIRException e) { 556 throw new FHIRException(e.getMessage(), e); 557 } 558 } 559 560 561 private static class Column { 562 String id; 563 String title; 564 String hint; 565 private String link; 566 567 protected Column(String id, String title, String hint) { 568 super(); 569 this.id = id; 570 this.title = title; 571 this.hint = hint; 572 } 573 protected Column(String id, String title, String hint, String link) { 574 super(); 575 this.id = id; 576 this.title = title; 577 this.hint = hint; 578 this.link = link; 579 } 580 581 } 582 public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, 583 boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean mustSupport, RenderingContext rc, String anchorPrefix) throws IOException, FHIRException { 584 assert(diff != snapshot);// check it's ok to get rid of one of these 585 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 586 gen.setTranslator(getTranslator()); 587 588 List<ElementDefinition> list; 589 if (diff) 590 list = supplementMissingDiffElements(profile); 591 else { 592 list = new ArrayList<>(); 593 list.addAll(profile.getSnapshot().getElement()); 594 } 595 596 List<Column> columns = new ArrayList<>(); 597 TableModel model; 598 switch (context.getStructureMode()) { 599 case BINDINGS: 600 scanBindings(columns, list); 601 model = initCustomTable(gen, corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER, columns); 602 break; 603 case OBLIGATIONS: 604 scanObligations(columns, list); 605 model = initCustomTable(gen, corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER, columns); 606 break; 607 case SUMMARY: 608 model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER, rc.getRules() == GenerationRules.IG_PUBLISHER ? TableGenerationMode.XHTML : TableGenerationMode.XML); 609 break; 610 default: 611 throw new Error("Unknown structure mode"); 612 } 613 614 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 615 profiles.add(profile); 616 keyRows.clear(); 617 618 genElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants, null, mustSupport, rc, anchorPrefix, profile, columns); 619 try { 620 return gen.generate(model, imagePath, 0, outputTracker); 621 } catch (org.hl7.fhir.exceptions.FHIRException e) { 622 throw new FHIRException(context.getWorker().formatMessage(I18nConstants.ERROR_GENERATING_TABLE_FOR_PROFILE__, profile.getUrl(), e.getMessage()), e); 623 } 624 } 625 626 private void scanBindings(List<Column> columns, List<ElementDefinition> list) { 627 Set<String> cols = new HashSet<>(); 628 scanBindings(cols, list, list.get(0)); 629 if (cols.contains("required")) { 630 columns.add(new Column("required", "Required", "Concepts must come from this value set")); 631 } 632 if (cols.contains("extensible")) { 633 columns.add(new Column("extensible", "Extensible", "Concepts must come from this value set if an appropriate concept is in the value set ")); 634 } 635 if (cols.contains("maximum")) { 636 columns.add(new Column("maximum", "Maximum", "A required binding for additional codes, for use when the binding strength is 'extensible' or 'preferred'")); 637 } 638 if (cols.contains("minimum")) { 639 columns.add(new Column("minimum", "Minimum", "The minimum allowable value set - any conformant system SHALL support all these codes")); 640 } 641 if (cols.contains("candidate")) { 642 columns.add(new Column("candidate", "Candidate", "This value set is a candidate to substitute for the overall conformance value set in some situations; usually these are defined in the documentation")); 643 } 644 if (cols.contains("current")) { 645 columns.add(new Column("current", "Current", "New records are required to use this value set, but legacy records may use other codes. The definition of 'new record' is difficult, since systems often create new records based on pre-existing data. Usually 'current' bindings are mandated by an external authority that makes clear rules around this")); 646 } 647 if (cols.contains("preferred")) { 648 columns.add(new Column("preferred", "Preferred", "This is the value set that is preferred in a given context (documentation should explain why)")); 649 } 650 if (cols.contains("ui")) { 651 columns.add(new Column("ui", "UI", "This value set is provided for user look up in a given context. Typically, these valuesets only include a subset of codes relevant for input in a context")); 652 } 653 if (cols.contains("starter")) { 654 columns.add(new Column("starter", "Starter", "This value set is a good set of codes to start with when designing your system")); 655 } 656 if (cols.contains("component")) { 657 columns.add(new Column("component", "Component", "This value set is a component of the base value set. Usually this is called out so that documentation can be written about a portion of the value set")); 658 } 659 if (cols.contains("example")) { 660 columns.add(new Column("example", "Example", "Instances are not expected or even encouraged to draw from the specified value set. The value set merely provides examples of the types of concepts intended to be included.")); 661 } 662 } 663 664 public void scanBindings(Set<String> cols, List<ElementDefinition> list, ElementDefinition ed) { 665 if (ed.hasBinding()) { 666 if (ed.getBinding().hasValueSet() && ed.getBinding().hasStrength()) { 667 switch (ed.getBinding().getStrength()) { 668 case EXAMPLE: 669 cols.add("example"); 670 break; 671 case EXTENSIBLE: 672 cols.add("extensible"); 673 break; 674 case PREFERRED: 675 cols.add("preferred"); 676 break; 677 case REQUIRED: 678 cols.add("required"); 679 break; 680 default: 681 break; 682 } 683 } 684 for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { 685 cols.add(ab.getPurpose().toCode()); 686 } 687 for (Extension ext : ed.getBinding().getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL)) { 688 cols.add(ext.getExtensionString("purpose")); 689 } 690 } 691 692 List<ElementDefinition> children = getChildren(list, ed); 693 for (ElementDefinition element : children) { 694 scanBindings(cols, list, element); 695 } 696 } 697 698 private void scanObligations(List<Column> columns, List<ElementDefinition> list) { 699 Set<String> cols = new HashSet<>(); 700 scanObligations(cols, list, list.get(0)); 701 702 if (cols.contains("$all")) { 703 columns.add(new Column("$all", "All Actors", "Obligations that apply to all actors")); 704 } 705 for (String col : cols) { 706 if (!"$all".equals(col)) { 707 ActorDefinition actor = context.getWorker().fetchResource(ActorDefinition.class, col); 708 if (actor == null) { 709 columns.add(new Column(col, tail(col), "Obligations that apply to the undefined actor "+col, col)); 710 } else { 711 columns.add(new Column(col, actor.getName(), "Obligations that apply to the actor "+actor.present(), actor.getWebPath())); 712 } 713 } 714 } 715 } 716 717 private void scanObligations(Set<String> cols, List<ElementDefinition> list, ElementDefinition ed) { 718 719 for (Extension ob : ed.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) { 720 if (ob.hasExtension("actor", "actorId")) { 721 for (Extension a : ob.getExtensionsByUrl("actor", "actorId")) { 722 cols.add(a.getValueCanonicalType().primitiveValue()); 723 } 724 } else 725 cols.add("$all"); 726 } 727 728 List<ElementDefinition> children = getChildren(list, ed); 729 for (ElementDefinition element : children) { 730 scanObligations(cols, list, element); 731 } 732 } 733 734 public TableModel initCustomTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, boolean alternating, String id, boolean isActive, List<Column> columns) throws IOException { 735 TableModel model = gen.new TableModel(id, isActive); 736 737 model.setAlternating(alternating); 738 if (context.getRules() == GenerationRules.VALID_RESOURCE || context.isInlineGraphics()) { 739 model.setDocoImg(HierarchicalTableGenerator.help16AsData()); 740 } else { 741 model.setDocoImg(Utilities.pathURL(prefix, "help16.png")); 742 } 743 model.setDocoRef(Utilities.pathURL("https://build.fhir.org/ig/FHIR/ig-guidance", "readingIgs.html#table-views")); 744 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Name"), translate("sd.hint", "The logical name of the element"), null, 0)); 745 for (Column col : columns) { 746 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", col.title), translate("sd.hint", col.hint), null, 0)); 747 } 748 return model; 749 } 750 751 private Row genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, 752 boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants, Row slicingRow, boolean mustSupport, RenderingContext rc, String anchorPrefix, Resource srcSD, List<Column> columns) throws IOException, FHIRException { 753 Row originalRow = slicingRow; 754 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 755 Row typesRow = null; 756 757 List<ElementDefinition> children = getChildren(all, element); 758 // if (!snapshot && isExtension && extensions != null && extensions != isExtension) 759 // return; 760 761 if (!onlyInformationIsMapping(all, element)) { 762 Row row = gen.new Row(); 763 row.setAnchor(element.getPath()); 764 row.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode)); 765 if (element.hasSlicing()) 766 row.setLineColor(1); 767 else if (element.hasSliceName()) 768 row.setLineColor(2); 769 else 770 row.setLineColor(0); 771 boolean hasDef = element != null; 772 boolean ext = false; 773 if (tail(element.getPath()).equals("extension")) { 774 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 775 row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 776 else 777 row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 778 ext = true; 779 } else if (tail(element.getPath()).equals("modifierExtension")) { 780 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 781 row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 782 else 783 row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 784 } else if (!hasDef || element.getType().size() == 0) { 785 if (root && profile != null && context.getWorker().getResourceNames().contains(profile.getType())) { 786 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 787 } else if (hasDef && element.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) { 788 row.setIcon("icon-object-box.png", HierarchicalTableGenerator.TEXT_ICON_OBJECT_BOX); 789 keyRows.add(element.getId()+"."+ToolingExtensions.readStringExtension(element, ToolingExtensions.EXT_JSON_PROP_KEY)); 790 } else { 791 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 792 } 793 } else if (hasDef && element.getType().size() > 1) { 794 if (allAreReference(element.getType())) { 795 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 796 } else if (element.hasExtension(ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) { 797 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 798 } else { 799 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 800 typesRow = row; 801 } 802 } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@")) { 803 row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE); 804 } else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode())) { 805 if (keyRows.contains(element.getId())) { 806 row.setIcon("icon-key.png", HierarchicalTableGenerator.TEXT_ICON_KEY); 807 } else { 808 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 809 } 810 } else if (hasDef && element.getType().get(0).hasTarget()) { 811 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 812 } else if (hasDef && isDataType(element.getType().get(0).getWorkingCode())) { 813 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 814 } else if (hasDef && element.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) { 815 row.setIcon("icon-object-box.png", HierarchicalTableGenerator.TEXT_ICON_OBJECT_BOX); 816 keyRows.add(element.getId()+"."+ToolingExtensions.readStringExtension(element, ToolingExtensions.EXT_JSON_PROP_KEY)); 817 } else if (hasDef && Utilities.existsInList(element.getType().get(0).getWorkingCode(), "Base", "Element", "BackboneElement")) { 818 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 819 } else { 820 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 821 } 822 if (element.hasUserData("render.opaque")) { 823 row.setOpacity("0.5"); 824 } 825 UnusedTracker used = new UnusedTracker(); 826 String ref = defPath == null ? null : defPath + anchorPrefix + element.getId(); 827 String sName = tail(element.getPath()); 828 if (element.hasSliceName()) 829 sName = sName +":"+element.getSliceName(); 830 used.used = true; 831 if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR)) 832 sName = "@"+sName; 833 Cell nc = genElementNameCell(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, all); 834 switch (context.getStructureMode()) { 835 case BINDINGS: 836 genElementBindings(gen, element, columns, row, profile, corePath); 837 break; 838 case OBLIGATIONS: 839 genElementObligations(gen, element, columns, row, corePath, profile); 840 break; 841 case SUMMARY: 842 genElementCells(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, nc, mustSupport, true, rc); 843 break; 844 845 } 846 if (element.hasSlicing()) { 847 if (standardExtensionSlicing(element)) { 848 used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing ... element.hasType() && element.getType().get(0).hasProfile(); 849 showMissing = false; //? 850 } else { 851 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 852 slicingRow = row; 853 for (Cell cell : row.getCells()) 854 for (Piece p : cell.getPieces()) { 855 p.addStyle("font-style: italic"); 856 } 857 } 858 } else if (element.hasSliceName()) { 859 row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM); 860 } 861 if (used.used || showMissing) 862 rows.add(row); 863 if (!used.used && !element.hasSlicing()) { 864 for (Cell cell : row.getCells()) 865 for (Piece p : cell.getPieces()) { 866 p.setStyle("text-decoration:line-through"); 867 p.setReference(null); 868 } 869 } else { 870 if (slicingRow != originalRow && !children.isEmpty()) { 871 // we've entered a slice; we're going to create a holder row for the slice children 872 Row hrow = gen.new Row(); 873 hrow.setAnchor(element.getPath()); 874 hrow.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode)); 875 hrow.setLineColor(1); 876 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 877 hrow.getCells().add(gen.new Cell(null, null, sName+":All Slices", "", null)); 878 switch (context.getStructureMode()) { 879 case BINDINGS: 880 case OBLIGATIONS: 881 for (Column col : columns) { 882 hrow.getCells().add(gen.new Cell()); 883 } 884 break; 885 case SUMMARY: 886 hrow.getCells().add(gen.new Cell()); 887 hrow.getCells().add(gen.new Cell()); 888 hrow.getCells().add(gen.new Cell()); 889 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null)); 890 break; 891 } 892 row.getSubRows().add(hrow); 893 row = hrow; 894 } 895 if (typesRow != null && !children.isEmpty()) { 896 // we've entered a typing slice; we're going to create a holder row for the all types children 897 Row hrow = gen.new Row(); 898 hrow.setAnchor(element.getPath()); 899 hrow.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode)); 900 hrow.setLineColor(1); 901 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 902 hrow.getCells().add(gen.new Cell(null, null, sName+":All Types", "", null)); 903 switch (context.getStructureMode()) { 904 case BINDINGS: 905 case OBLIGATIONS: 906 for (Column col : columns) { 907 hrow.getCells().add(gen.new Cell()); 908 } 909 break; 910 case SUMMARY: 911 hrow.getCells().add(gen.new Cell()); 912 hrow.getCells().add(gen.new Cell()); 913 hrow.getCells().add(gen.new Cell()); 914 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null)); 915 } 916 row.getSubRows().add(hrow); 917 row = hrow; 918 } 919 920 Row currRow = row; 921 List<ElementChoiceGroup> groups = readChoices(element, children); 922 boolean isExtension = Utilities.existsInList(tail(element.getPath()), "extension", "modifierExtension"); 923 if (!element.prohibited()) { 924 for (ElementDefinition child : children) { 925 if (!child.hasSliceName()) { 926 currRow = row; 927 } 928 Row childRow = chooseChildRowByGroup(gen, currRow, groups, child, element, isConstraintMode); 929 930 if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) { 931 currRow = genElement(defPath, gen, childRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow, mustSupport, rc, anchorPrefix, srcSD, columns); 932 } 933 } 934 } 935 // if (!snapshot && (extensions == null || !extensions)) 936 // for (ElementDefinition child : children) 937 // if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension")) 938 // genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants); 939 } 940 if (typesRow != null && !element.prohibited() && context.getStructureMode() == StructureDefinitionRendererMode.SUMMARY) { 941 makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName, mustSupport, srcSD); 942 } 943 } 944 return slicingRow; 945 } 946 947 private void genElementObligations(HierarchicalTableGenerator gen, ElementDefinition element, List<Column> columns, Row row, String corePath, StructureDefinition profile) throws IOException { 948 for (Column col : columns) { 949 Cell gc = gen.new Cell(); 950 row.getCells().add(gc); 951 ObligationsRenderer obr = new ObligationsRenderer(corePath, profile, element.getPath(), context, null, this); 952 obr.seeObligations(element, col.id); 953 obr.renderList(gen, gc); 954 } 955 } 956 957 private String displayForUsage(Coding c) { 958 if (c.hasDisplay()) { 959 return c.getDisplay(); 960 } 961 if ("http://terminology.hl7.org/CodeSystem/usage-context-type".equals(c.getSystem())) { 962 return c.getCode(); 963 } 964 return c.getCode(); 965 } 966 967 private void genElementBindings(HierarchicalTableGenerator gen, ElementDefinition element, List<Column> columns, Row row, StructureDefinition profile, String corepath) { 968 for (Column col : columns) { 969 Cell gc = gen.new Cell(); 970 row.getCells().add(gc); 971 List<ElementDefinitionBindingAdditionalComponent> bindings = collectBindings(element, col.id); 972 if (bindings.size() > 0) { 973 Piece p = gen.new Piece(null); 974 gc.addPiece(p); 975 new AdditionalBindingsRenderer(context.getPkp(), corepath, profile, element.getPath(), context, null, this).render(p.getChildren(), bindings); 976 } 977 } 978 } 979 980 private List<ElementDefinitionBindingAdditionalComponent> collectBindings(ElementDefinition element, String type) { 981 List<ElementDefinitionBindingAdditionalComponent> res = new ArrayList<>(); 982 if (element.hasBinding()) { 983 ElementDefinitionBindingComponent b = element.getBinding(); 984 if (b.hasStrength() && type.equals(b.getStrength().toCode())) { 985 ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent(); 986 res.add(ab.setAny(false).setDocumentation(b.getDescription()).setValueSet(b.getValueSet())); 987 } 988 if ("maximum".equals(type) && b.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) { 989 ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent(); 990 res.add(ab.setAny(false).setValueSet(ToolingExtensions.readStringExtension(b, ToolingExtensions.EXT_MAX_VALUESET))); 991 } 992 if ("minimum".equals(type) && b.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) { 993 ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent(); 994 res.add(ab.setAny(false).setValueSet(ToolingExtensions.readStringExtension(b, ToolingExtensions.EXT_MIN_VALUESET))); 995 } 996 for (ElementDefinitionBindingAdditionalComponent t : b.getAdditional()) { 997 if (type.equals(t.getPurpose().toCode())) { 998 res.add(t); 999 } 1000 } 1001 for (Extension ext : b.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL)) { 1002 if (type.equals(ext.getExtensionString("purpose"))) { 1003 ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent(); 1004 if (ext.hasExtension("any")) { 1005 ab.setAny(ToolingExtensions.readBooleanExtension(ext, "any")); 1006 } 1007 if (ext.hasExtension("purpose")) { 1008 ab.setPurpose(AdditionalBindingPurposeVS.fromCode(ToolingExtensions.readStringExtension(ext, "purpose"))); 1009 } 1010 if (ext.hasExtension("documentation")) { 1011 ab.setDocumentation(ToolingExtensions.readStringExtension(ext, "documentation")); 1012 } 1013 if (ext.hasExtension("shortDoco")) { 1014 ab.setShortDoco(ToolingExtensions.readStringExtension(ext, "shortDoco")); 1015 } 1016 if (ToolingExtensions.hasExtension(ext, "usage")) { 1017 ab.addUsage(ext.getExtensionByUrl("usage").getValueUsageContext()); 1018 } 1019 if (ext.hasExtension("valueSet")) { 1020 ab.setValueSet(ToolingExtensions.readStringExtension(ext, "valueSet")); 1021 } 1022 res.add(ab); 1023 } 1024 } 1025 } 1026 return res; 1027 } 1028 1029 public Cell genElementNameCell(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath, 1030 String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef, 1031 boolean ext, UnusedTracker used, String ref, String sName, List<ElementDefinition> elements) throws IOException { 1032 String hint = ""; 1033 hint = checkAdd(hint, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : "")); 1034 if (hasDef && element.hasDefinition()) { 1035 hint = checkAdd(hint, (hasDef && element.hasSliceName() ? ": " : "")); 1036 hint = checkAdd(hint, !hasDef ? null : gt(element.getDefinitionElement())); 1037 } 1038 if (element.hasSlicing() && slicesExist(elements, element)) { // some elements set up slicing but don't actually slice, so we don't augment the name 1039 sName = "Slices for "+sName; 1040 } 1041 Cell left = gen.new Cell(null, ref, sName, hint, null); 1042 row.getCells().add(left); 1043 return left; 1044 } 1045 1046 public List<Cell> genElementCells(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath, 1047 String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef, 1048 boolean ext, UnusedTracker used, String ref, String sName, Cell nameCell, boolean mustSupport, boolean allowSubRows, RenderingContext rc) throws IOException { 1049 List<Cell> res = new ArrayList<>(); 1050 Cell gc = gen.new Cell(); 1051 row.getCells().add(gc); 1052 res.add(gc); 1053 if (element != null && element.getIsModifier()) { 1054 checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false)); 1055 } 1056 if (element != null) { 1057 if (element.getMustSupport() && element.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) { 1058 checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element has obligations and must be supported"), "SO", "white", "red", null, false)); 1059 } else if (element.getMustSupport()) { 1060 checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false)); 1061 } else if (element != null && element.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) { 1062 checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element has obligations"), "O", "white", "red", null, false)); 1063 } 1064 } 1065 if (element != null && element.getIsSummary()) { 1066 checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false)); 1067 } 1068 if (element != null && element.getMustHaveValue()) { 1069 checkForNoChange(element.getMustHaveValueElement(), gc.addStyledText(translate("sd.table", "This primitive element must have a value"), "V", "maroon", null, null, true)); 1070 } 1071 if (element != null && (hasNonBaseConstraints(element.getConstraint()) || hasNonBaseConditions(element.getCondition()))) { 1072 Piece p = gc.addText(CONSTRAINT_CHAR); 1073 p.setHint(translate("sd.table", "This element has or is affected by constraints ("+listConstraintsAndConditions(element)+")")); 1074 p.addStyle(CONSTRAINT_STYLE); 1075 p.setReference(Utilities.pathURL(VersionUtilities.getSpecUrl(context.getWorker().getVersion()), "conformance-rules.html#constraints")); 1076 } 1077 if (element != null && element.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) { 1078 StandardsStatus ss = StandardsStatus.fromCode(element.getExtensionString(ToolingExtensions.EXT_STANDARDS_STATUS)); 1079 gc.addStyledText("Standards Status = "+ss.toDisplay(), ss.getAbbrev(), "black", ss.getColor(), context.getWorker().getSpecUrl()+"versions.html#std-process", true); 1080 } 1081 1082 ExtensionContext extDefn = null; 1083 if (ext) { 1084 if (element != null) { 1085 if (element.getType().size() == 1 && element.getType().get(0).hasProfile()) { 1086 String eurl = element.getType().get(0).getProfile().get(0).getValue(); 1087 extDefn = locateExtension(StructureDefinition.class, eurl); 1088 if (extDefn == null) { 1089 res.add(genCardinality(gen, element, row, hasDef, used, null)); 1090 res.add(addCell(row, gen.new Cell(null, null, "?gen-e1? "+element.getType().get(0).getProfile(), null, null))); 1091 res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(ProfileUtilities.UD_DERIVATION_POINTER), used.used, profile == null ? "" : profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport, allowSubRows, rc)); 1092 } else { 1093 String name = element.hasSliceName() ? element.getSliceName() : urltail(eurl); 1094 nameCell.getPieces().get(0).setText(name); 1095 // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename")); 1096 nameCell.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl()); 1097 res.add(genCardinality(gen, element, row, hasDef, used, extDefn.getElement())); 1098 ElementDefinition valueDefn = extDefn.getExtensionValueDefinition(); 1099 if (valueDefn != null && !"0".equals(valueDefn.getMax())) 1100 res.add(genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath, root, mustSupport)); 1101 else // if it's complex, we just call it nothing 1102 // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile); 1103 res.add(addCell(row, gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null))); 1104 res.add(generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot, mustSupport, allowSubRows, rc)); 1105 } 1106 } else { 1107 res.add(genCardinality(gen, element, row, hasDef, used, null)); 1108 if ("0".equals(element.getMax())) 1109 res.add(addCell(row, gen.new Cell())); 1110 else 1111 res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport)); 1112 res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(ProfileUtilities.UD_DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport, allowSubRows, rc)); 1113 } 1114 } 1115 } else if (element != null) { 1116 res.add(genCardinality(gen, element, row, hasDef, used, null)); 1117 if (hasDef && !"0".equals(element.getMax()) && typesRow == null) 1118 res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport)); 1119 else 1120 res.add(addCell(row, gen.new Cell())); 1121 res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(ProfileUtilities.UD_DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport, allowSubRows, rc)); 1122 } 1123 return res; 1124 } 1125 1126 private Cell genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) { 1127 IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 1128 StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 1129 if (min.isEmpty() && definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER) != null) { 1130 ElementDefinition base = (ElementDefinition) definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER); 1131 if (base.hasMinElement()) { 1132 min = base.getMinElement().copy(); 1133 min.setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, true); 1134 } 1135 } 1136 if (max.isEmpty() && definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER) != null) { 1137 ElementDefinition base = (ElementDefinition) definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER); 1138 if (base.hasMaxElement()) { 1139 max = base.getMaxElement().copy(); 1140 max.setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, true); 1141 } 1142 } 1143 if (min.isEmpty() && fallback != null) 1144 min = fallback.getMinElement(); 1145 if (max.isEmpty() && fallback != null) 1146 max = fallback.getMaxElement(); 1147 1148 if (!max.isEmpty()) 1149 tracker.used = !max.getValue().equals("0"); 1150 1151 String hint = null; 1152 if (max.hasValue() && min.hasValue() && "*".equals(max.getValue()) && 0 == min.getValue()) { 1153 if (definition.hasExtension(ToolingExtensions.EXT_JSON_EMPTY)) { 1154 String code = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY); 1155 if ("present".equals(code)) { 1156 hint = "This element is present as a JSON Array even when there are no items in the instance"; 1157 } else { 1158 hint = "This element may be present as a JSON Array even when there are no items in the instance"; 1159 } 1160 } 1161 } 1162 Cell cell = gen.new Cell(null, null, null, null, null); 1163 row.getCells().add(cell); 1164 if (!min.isEmpty() || !max.isEmpty()) { 1165 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), hint))); 1166 cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", hint))); 1167 cell.addPiece(checkForNoChange(max, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), hint))); 1168 } 1169 return cell; 1170 } 1171 1172 public List<ElementDefinition> supplementMissingDiffElements(StructureDefinition profile) { 1173 List<ElementDefinition> list = new ArrayList<>(); 1174 list.addAll(profile.getDifferential().getElement()); 1175 if (list.isEmpty()) { 1176 ElementDefinition root = new ElementDefinition().setPath(profile.getTypeName()); 1177 root.setId(profile.getTypeName()); 1178 list.add(root); 1179 } else { 1180 if (list.get(0).getPath().contains(".")) { 1181 ElementDefinition root = new ElementDefinition().setPath(profile.getTypeName()); 1182 root.setId(profile.getTypeName()); 1183 list.add(0, root); 1184 } 1185 } 1186 insertMissingSparseElements(list); 1187 return list; 1188 } 1189 1190 private boolean usesMustSupport(List<ElementDefinition> list) { 1191 for (ElementDefinition ed : list) 1192 if (ed.hasMustSupport() && ed.getMustSupport()) 1193 return true; 1194 return false; 1195 } 1196 1197 1198 1199 private Row chooseChildRowByGroup(HierarchicalTableGenerator gen, Row row, List<ElementChoiceGroup> groups, ElementDefinition element, ElementDefinition parent, boolean isConstraintMode) { 1200 String name = tail(element.getPath()); 1201 for (ElementChoiceGroup grp : groups) { 1202 if (grp.getElements().contains(name)) { 1203 if (grp.getRow() == null) { 1204 grp.setRow(makeChoiceElementRow(gen, row, grp, parent, isConstraintMode)); 1205 } 1206 return grp.getRow(); 1207 } 1208 } 1209 return row; 1210 } 1211 1212 private Row makeChoiceElementRow(HierarchicalTableGenerator gen, Row prow, ElementChoiceGroup grp, ElementDefinition parent, boolean isConstraintMode) { 1213 if (context.getStructureMode() != StructureDefinitionRendererMode.SUMMARY) { 1214 return prow; 1215 } 1216 Row row = gen.new Row(); 1217 row.setAnchor(parent.getPath()+"-"+grp.getName()); 1218 row.setColor(context.getProfileUtilities().getRowColor(parent, isConstraintMode)); 1219 row.setLineColor(1); 1220 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 1221 row.getCells().add(gen.new Cell(null, null, "(Choice of one)", "", null)); 1222 row.getCells().add(gen.new Cell()); 1223 row.getCells().add(gen.new Cell(null, null, (grp.isMandatory() ? "1" : "0")+"..1", "", null)); 1224 row.getCells().add(gen.new Cell()); 1225 row.getCells().add(gen.new Cell()); 1226 prow.getSubRows().add(row); 1227 return row; 1228 } 1229 1230 1231 private void insertMissingSparseElements(List<ElementDefinition> list) { 1232 int i = 1; 1233 while (i < list.size()) { 1234 String[] pathCurrent = list.get(i).getPath().split("\\."); 1235 String[] pathLast = list.get(i-1).getPath().split("\\."); 1236 int firstDiff = 0; // the first entry must be a match 1237 while (firstDiff < pathCurrent.length && firstDiff < pathLast.length && pathCurrent[firstDiff].equals(pathLast[firstDiff])) { 1238 firstDiff++; 1239 } 1240 if (!(isSibling(pathCurrent, pathLast, firstDiff) || isChild(pathCurrent, pathLast, firstDiff))) { 1241 // now work backwards down to lastMatch inserting missing path nodes 1242 ElementDefinition parent = findParent(list, i, list.get(i).getPath()); 1243 int parentDepth = Utilities.charCount(parent.getPath(), '.')+1; 1244 int childDepth = Utilities.charCount(list.get(i).getPath(), '.')+1; 1245 if (childDepth > parentDepth + 1) { 1246 String basePath = parent.getPath(); 1247 String baseId = parent.getId(); 1248 for (int index = parentDepth; index >= firstDiff; index--) { 1249 String mtail = makeTail(pathCurrent, parentDepth, index); 1250 ElementDefinition root = new ElementDefinition().setPath(basePath+"."+mtail); 1251 root.setId(baseId+"."+mtail); 1252 list.add(i, root); 1253 } 1254 } 1255 } 1256 i++; 1257 } 1258 } 1259 1260 1261 private String urltail(String path) { 1262 if (path.contains("#")) 1263 return path.substring(path.lastIndexOf('#')+1); 1264 if (path.contains("/")) 1265 return path.substring(path.lastIndexOf('/')+1); 1266 else 1267 return path; 1268 1269 } 1270 1271 private boolean standardExtensionSlicing(ElementDefinition element) { 1272 String t = tail(element.getPath()); 1273 return (t.equals("extension") || t.equals("modifierExtension")) 1274 && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getPath().equals("url") && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE); 1275 } 1276 1277 public Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, boolean snapshot, boolean mustSupportOnly, boolean allowSubRows, RenderingContext rc) throws IOException, FHIRException { 1278 return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot, mustSupportOnly, allowSubRows, rc); 1279 } 1280 1281 public Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, ElementDefinition valueDefn, boolean snapshot, boolean mustSupportOnly, boolean allowSubRows, RenderingContext rc) throws IOException, FHIRException { 1282 Cell c = gen.new Cell(); 1283 row.getCells().add(c); 1284 1285 if (used) { 1286 if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) { 1287 if (root) { 1288 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 1289 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 1290 } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 1291 !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) { 1292 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 1293 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 1294 } 1295 } 1296 if (root) { 1297 if (profile != null && profile.getAbstract()) { 1298 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1299 c.addPiece(gen.new Piece(null, "This is an abstract "+(profile.getDerivation() == TypeDerivationRule.CONSTRAINT ? "profile" : "type")+". ", null)); 1300 1301 List<StructureDefinition> children = new ArrayList<>(); 1302 for (StructureDefinition sd : context.getWorker().fetchResourcesByType(StructureDefinition.class)) { 1303 if (sd.hasBaseDefinition() && sd.getBaseDefinition().equals(profile.getUrl())) { 1304 children.add(sd); 1305 } 1306 } 1307 if (!children.isEmpty()) { 1308 c.addPiece(gen.new Piece(null, "Child "+(profile.getDerivation() == TypeDerivationRule.CONSTRAINT ? "profiles" : "types")+": ", null)); 1309 boolean first = true; 1310 for (StructureDefinition sd : children) { 1311 if (first) first = false; else c.addPiece(gen.new Piece(null, ", ", null)); 1312 c.addPiece(gen.new Piece(sd.getWebPath(), sd.getTypeName(), null)); 1313 } 1314 } 1315 } 1316 } 1317 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 1318 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 1319 } else { 1320 if (definition != null && definition.hasShort()) { 1321 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1322 c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null))); 1323 } else if (fallback != null && fallback.hasShort()) { 1324 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1325 c.addPiece(gen.new Piece(null, gt(fallback.getShortElement()), null).addStyle("opacity: 0.5")); 1326 } 1327 if (url != null) { 1328 if (!c.getPieces().isEmpty()) 1329 c.addPiece(gen.new Piece("br")); 1330 String fullUrl = url.startsWith("#") ? baseURL+url : url; 1331 StructureDefinition ed = context.getWorker().fetchResource(StructureDefinition.class, url, profile); 1332 String ref = null; 1333 String ref2 = null; 1334 String fixedUrl = null; 1335 if (ed != null) { 1336 String p = ed.getWebPath(); 1337 if (p != null) { 1338 ref = p.startsWith("http:") || context.getRules() == GenerationRules.IG_PUBLISHER ? p : Utilities.pathURL(corePath, p); 1339 } 1340 fixedUrl = getFixedUrl(ed); 1341 if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension? 1342 if (fixedUrl.equals(url)) 1343 fixedUrl = null; 1344 else { 1345 StructureDefinition ed2 = context.getWorker().fetchResource(StructureDefinition.class, fixedUrl); 1346 if (ed2 != null) { 1347 String p2 = ed2.getWebPath(); 1348 if (p2 != null) { 1349 ref2 = p2.startsWith("http:") || context.getRules() == GenerationRules.IG_PUBLISHER ? p2 : Utilities.pathURL(corePath, p2); 1350 } 1351 } 1352 } 1353 } 1354 } 1355 if (fixedUrl == null) { 1356 if (!Utilities.noString(fullUrl)) { 1357 c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold")); 1358 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 1359 } 1360 } else { 1361 // reference to a profile take on the extension show the base URL 1362 c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold")); 1363 c.getPieces().add(gen.new Piece(ref2, fixedUrl, null)); 1364 c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold")); 1365 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 1366 1367 } 1368 } 1369 1370 if (definition.hasSlicing()) { 1371 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1372 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold")); 1373 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 1374 } 1375 if (!definition.getPath().contains(".") && ToolingExtensions.hasExtension(profile, ToolingExtensions.EXT_BINDING_STYLE)) { 1376 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1377 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")); 1378 c.getPieces().add(gen.new Piece(null, "This type can be bound to a value set using the ", null)); 1379 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_BINDING_STYLE), null)); 1380 c.getPieces().add(gen.new Piece(null, " binding style", null)); 1381 } 1382 if (definition.hasValueAlternatives()) { 1383 addCanonicalList(gen, c, definition.getValueAlternatives(), "The primitive value may be replaced by the extension", true); 1384 } 1385 if (definition.hasExtension(ToolingExtensions.EXT_IMPLIED_PREFIX)) { 1386 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1387 c.getPieces().add(gen.new Piece(null, "When this element is read ", null)); 1388 Piece piece = gen.new Piece("code"); 1389 piece.addHtml(new XhtmlNode(NodeType.Text).setContent(ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_IMPLIED_PREFIX))); 1390 c.getPieces().add(piece); 1391 c.getPieces().add(gen.new Piece(null, " is prefixed to the value before validation", null)); 1392 } 1393 1394 if (definition.hasExtension(ToolingExtensions.EXT_EXTENSION_STYLE)) { 1395 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1396 String es = definition.getExtensionString(ToolingExtensions.EXT_EXTENSION_STYLE); 1397 if ("named-elements".equals(es)) { 1398 if (rc.hasLink(KnownLinkType.JSON_NAMES)) { 1399 c.getPieces().add(gen.new Piece(rc.getLink(KnownLinkType.JSON_NAMES), "This element can be extended by named JSON elements", null)); 1400 } else { 1401 c.getPieces().add(gen.new Piece(ToolingExtensions.WEB_EXTENSION_STYLE, "This element can be extended by named JSON elements", null)); 1402 } 1403 } 1404 } 1405 if (definition.hasExtension(ToolingExtensions.EXT_DATE_FORMAT)) { 1406 String df = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_DATE_FORMAT); 1407 if (df != null) { 1408 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1409 c.getPieces().add(gen.new Piece(null, "Date Format: "+df, null)); 1410 } 1411 } 1412 if (definition.hasExtension(ToolingExtensions.EXT_ID_EXPECTATION)) { 1413 String ide = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_ID_EXPECTATION); 1414 if (ide.equals("optional")) { 1415 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1416 c.getPieces().add(gen.new Piece(null, "Id may or not be present (this is the default for elements but not resources)", null)); 1417 } else if (ide.equals("required")) { 1418 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1419 c.getPieces().add(gen.new Piece(null, "Id is required to be present (this is the default for resources but not elements)", null)); 1420 } else if (ide.equals("required")) { 1421 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1422 c.getPieces().add(gen.new Piece(null, "An ID is not allowed in this context", null)); 1423 } 1424 } 1425 if (definition.hasExtension(ToolingExtensions.EXT_XML_NAME)) { 1426 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1427 if (definition.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE)) { 1428 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML")+": ", null).addStyle("font-weight:bold")); 1429 c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAME), null)); 1430 c.getPieces().add(gen.new Piece(null, " (", null)); 1431 c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAMESPACE), null)); 1432 c.getPieces().add(gen.new Piece(null, ")", null)); 1433 } else { 1434 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Element Name")+": ", null).addStyle("font-weight:bold")); 1435 c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAME), null)); 1436 } 1437 } else if (definition.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE)) { 1438 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1439 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 1440 c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAMESPACE), null)); 1441 } 1442 if (definition.hasExtension(ToolingExtensions.EXT_JSON_EMPTY)) { 1443 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1444 String code = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY); 1445 if ("present".equals(code)) { 1446 c.getPieces().add(gen.new Piece(null, "JSON: This element is present as a JSON Array even when there are no items in the instance", null)); 1447 } else { 1448 c.getPieces().add(gen.new Piece(null, "JSON: This element may be present as a JSON Array even when there are no items in the instance", null)); 1449 } 1450 } 1451 String jn = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_NAME); 1452 if (!Utilities.noString(jn)) { 1453 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1454 if (definition.getPath().contains(".")) { 1455 c.getPieces().add(gen.new Piece(null, translate("sd.table", "JSON Property Name")+": ", null).addStyle("font-weight:bold")); 1456 c.getPieces().add(gen.new Piece(null, jn, null)); 1457 } else { 1458 c.getPieces().add(gen.new Piece(null, translate("sd.table", "JSON Property Name for Type")+": ", null).addStyle("font-weight:bold")); 1459 Piece piece = gen.new Piece("code"); 1460 piece.addHtml(new XhtmlNode(NodeType.Text).setContent(jn)); 1461 c.getPieces().add(piece); 1462 } 1463 } 1464 1465 if (ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) { 1466 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1467 c.getPieces().add(gen.new Piece(null, "JSON: The type of this element is inferred from the JSON type in the instance", null)); 1468 } 1469 if (ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_NULLABLE)) { 1470 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1471 c.getPieces().add(gen.new Piece(null, "JSON: This object can be represented as null in the JSON structure (which counts as 'present' for cardinality purposes)", null)); 1472 } 1473 if (definition.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) { 1474 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1475 String code = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_PROP_KEY); 1476 c.getPieces().add(gen.new Piece(null, "JSON: Represented as a single JSON Object with named properties using the value of the "+code+" child as the key", null)); 1477 } 1478 if (definition.hasExtension(ToolingExtensions.EXT_TYPE_SPEC)) { 1479 for (Extension e : definition.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) { 1480 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1481 String cond = ToolingExtensions.readStringExtension(e, "condition"); 1482 String type = ToolingExtensions.readStringExtension(e, "type"); 1483 c.getPieces().add(gen.new Piece(null, "JSON: If ", null)); 1484 Piece piece = gen.new Piece("code"); 1485 piece.addHtml(new XhtmlNode(NodeType.Text).setContent(cond)); 1486 c.getPieces().add(piece); 1487 c.getPieces().add(gen.new Piece(null, "then the type is ", null)); 1488 StructureDefinition sd = context.getWorker().fetchTypeDefinition(type); 1489 if (sd == null) { 1490 c.getPieces().add(gen.new Piece("<code>")); 1491 c.getPieces().add(gen.new Piece(null, type, null)); 1492 c.getPieces().add(gen.new Piece("</code>")); 1493 } else { 1494 c.getPieces().add(gen.new Piece(sd.getWebPath(), sd.getTypeName(), null)); 1495 } 1496 } 1497 } 1498 if (root) { 1499 if (ToolingExtensions.readBoolExtension(profile, ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG)) { 1500 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1501 c.addPiece(gen.new Piece(null, "This is an obligation profile that only contains obligations and additional bindings", null).addStyle("font-weight:bold")); 1502 } 1503 addCanonicalListExt(gen, c, profile.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_INHERITS), "This profile picks up obligations and additional bindings from the profile", true); 1504 addCanonicalListExt(gen, c, profile.getExtensionsByUrl(ToolingExtensions.EXT_SD_IMPOSE_PROFILE), "This profile also imposes the profile", true); 1505 addCanonicalListExt(gen, c, profile.getExtensionsByUrl(ToolingExtensions.EXT_SD_COMPLIES_WITH_PROFILE), "This profile also complies with the profile", true); 1506 1507 if (profile.getKind() == StructureDefinitionKind.LOGICAL) { 1508 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1509 if (ToolingExtensions.readBoolExtension(profile, ToolingExtensions.EXT_LOGICAL_TARGET)) { 1510 c.addPiece(gen.new Piece(null, "This logical model can be the target of a reference", null).addStyle("font-weight:bold")); 1511 } else { 1512 c.addPiece(gen.new Piece(null, "This logical model cannot be the target of a reference", null).addStyle("font-weight:bold")); 1513 } 1514 } 1515 } 1516 if (definition != null) { 1517 ElementDefinitionBindingComponent binding = null; 1518 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 1519 binding = makeUnifiedBinding(valueDefn.getBinding(), valueDefn); 1520 else if (definition.hasBinding()) 1521 binding = makeUnifiedBinding(definition.getBinding(), definition); 1522 if (binding!=null && !binding.isEmpty()) { 1523 if (!c.getPieces().isEmpty()) 1524 c.addPiece(gen.new Piece("br")); 1525 BindingResolution br = context.getPkp() == null ? makeNullBr(binding) : context.getPkp().resolveBinding(profile, binding, definition.getPath()); 1526 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"))); 1527 c.getPieces().add(checkForNoChange(binding.getValueSetElement(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, null))); 1528 if (binding.hasStrength()) { 1529 c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, " (", null))); 1530 c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition()))); 1531 c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, ")", null))); 1532 } 1533 if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) { 1534 c.getPieces().add(gen.new Piece(null, ": ", null)); 1535 c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement()).asStringValue(), checkForNoChange(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement()))); 1536 } 1537 1538 AdditionalBindingsRenderer abr = new AdditionalBindingsRenderer(context.getPkp(), corePath, profile, definition.getPath(), rc, null, this); 1539 if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) { 1540 abr.seeMaxBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MAX_VALUESET)); 1541 } 1542 if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) { 1543 abr.seeMinBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MIN_VALUESET)); 1544 } 1545 if (binding.hasExtension(ToolingExtensions.EXT_BINDING_ADDITIONAL)) { 1546 abr.seeAdditionalBindings(binding.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL)); 1547 } 1548 abr.render(gen, c); 1549 } 1550 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 1551 if (!inv.hasSource() || profile == null || inv.getSource().equals(profile.getUrl()) || allInvariants) { 1552 if (!c.getPieces().isEmpty()) 1553 c.addPiece(gen.new Piece("br")); 1554 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 1555 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null))); 1556 } 1557 } 1558 if ((definition.hasBase() && "*".equals(definition.getBase().getMax())) || (definition.hasMax() && "*".equals(definition.getMax()))) { 1559 if (c.getPieces().size() > 0) 1560 c.addPiece(gen.new Piece("br")); 1561 if (definition.hasOrderMeaning()) { 1562 c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null)); 1563 } else { 1564 // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null)); 1565 } 1566 } 1567 if (definition.hasFixed()) { 1568 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1569 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold"))); 1570 if (!useTableForFixedValues || !allowSubRows || definition.getFixed().isPrimitive()) { 1571 String s = buildJson(definition.getFixed()); 1572 String link = null; 1573 if (Utilities.isAbsoluteUrl(s) && context.getPkp() != null) 1574 link = context.getPkp().getLinkForUrl(corePath, s); 1575 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 1576 } else { 1577 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "As shown", null).addStyle("color: darkgreen"))); 1578 genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath, false); 1579 } 1580 if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) { 1581 Piece p = describeCoded(gen, definition.getFixed()); 1582 if (p != null) 1583 c.getPieces().add(p); 1584 } 1585 } else if (definition.hasPattern()) { 1586 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1587 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold"))); 1588 if (!useTableForFixedValues || !allowSubRows || definition.getPattern().isPrimitive()) 1589 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 1590 else { 1591 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen"))); 1592 genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath, mustSupportOnly); 1593 } 1594 } else if (definition.hasExample()) { 1595 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 1596 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1597 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel())+": ", null).addStyle("font-weight:bold"))); 1598 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 1599 } 1600 } 1601 1602 ObligationsRenderer obr = new ObligationsRenderer(corePath, profile, definition.getPath(), rc, null, this); 1603 if (definition.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) { 1604 obr.seeObligations(definition.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)); 1605 } 1606 if (!definition.getPath().contains(".") && profile.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) { 1607 obr.seeObligations(profile.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)); 1608 } 1609 obr.renderTable(gen, c); 1610 1611 if (definition.hasMaxLength() && definition.getMaxLength()!=0) { 1612 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1613 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 1614 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 1615 } 1616 if (profile != null) { 1617 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 1618 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 1619 ElementDefinitionMappingComponent map = null; 1620 for (ElementDefinitionMappingComponent m : definition.getMapping()) 1621 if (m.getIdentity().equals(md.getIdentity())) 1622 map = m; 1623 if (map != null) { 1624 for (int i = 0; i<definition.getMapping().size(); i++){ 1625 c.addPiece(gen.new Piece("br")); 1626 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null)); 1627 } 1628 } 1629 } 1630 } 1631 } 1632 } 1633 } 1634 } 1635 return c; 1636 } 1637 1638 1639 private void addCanonicalListExt(HierarchicalTableGenerator gen, Cell c, List<Extension> list, String start, boolean bold) { 1640 List<CanonicalType> clist = new ArrayList<>(); 1641 for (Extension ext : list) { 1642 if (ext.hasValueCanonicalType()) { 1643 clist.add(ext.getValueCanonicalType()); 1644 } 1645 } 1646 addCanonicalList(gen, c, clist, start, bold); 1647 } 1648 1649 private void addCanonicalList(HierarchicalTableGenerator gen, Cell c, List<CanonicalType> list, String start, boolean bold) { 1650 if (!list.isEmpty()) { 1651 1652 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 1653 Piece p = gen.new Piece(null, start+(list.size() != 1 ? "s" : "")+" ", null); 1654 c.addPiece(p); 1655 if (bold) p.addStyle("font-weight:bold"); 1656 1657 for (int i = 0; i < list.size(); i++) { 1658 CanonicalType ct = list.get(i); 1659 if (i > 0) { 1660 if (i < list.size() - 1) { 1661 c.addPiece(gen.new Piece(null, ", ", null)); 1662 } else { 1663 c.addPiece(gen.new Piece(null, " and ", null)); 1664 } 1665 } 1666 String iu = ct.primitiveValue(); 1667 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, iu); 1668 if (sd == null) { 1669 p = gen.new Piece(null, iu, null).addStyle("font-weight:bold"); 1670 c.addPiece(p); 1671 } else if (sd.hasWebPath()) { 1672 p = gen.new Piece(sd.getWebPath(), sd.present(), null).addStyle("font-weight:bold"); 1673 c.addPiece(p); 1674 } else { 1675 p = gen.new Piece(iu, sd.present(), null).addStyle("font-weight:bold"); 1676 c.addPiece(p); 1677 } 1678 if (bold) p.addStyle("font-weight:bold"); 1679 } 1680 } 1681 } 1682 1683 private Piece checkForNoChange(Element source, Piece piece) { 1684 if (source.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS)) { 1685 piece.addStyle("opacity: 0.5"); 1686 } 1687 return piece; 1688 } 1689 1690 private String checkForNoChange(Element source) { 1691 if (source.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS)) { 1692 return "opacity: 0.5"; 1693 } else { 1694 return null; 1695 } 1696 } 1697 1698 private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean mustSupportMode) { 1699 Cell c = gen.new Cell(); 1700 r.getCells().add(c); 1701 if (e.hasContentReference()) { 1702 ElementInStructure ed = getElementByName(profile.getSnapshot().getElement(), e.getContentReference(), profile); 1703 if (ed == null) 1704 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", e.getContentReference()), null)); 1705 else { 1706 if (ed.getSource() == profile) { 1707 c.getPieces().add(gen.new Piece(null, translate("sd.table", "See ", ed.getElement().getPath()), null)); 1708 c.getPieces().add(gen.new Piece("#"+ed.getElement().getPath(), tail(ed.getElement().getPath()), ed.getElement().getPath())); 1709 } else { 1710 c.getPieces().add(gen.new Piece(null, translate("sd.table", "See ", ed.getElement().getPath()), null)); 1711 c.getPieces().add(gen.new Piece(pfx(corePath, ed.getSource().getWebPath())+"#"+ed.getElement().getPath(), tail(ed.getElement().getPath())+" ("+ed.getSource().getTypeName()+")", ed.getElement().getPath())); 1712 } 1713 } 1714 return c; 1715 } 1716 List<TypeRefComponent> types = e.getType(); 1717 if (!e.hasType()) { 1718 if (root) { // we'll use base instead of types then 1719 StructureDefinition bsd = profile == null ? null : context.getWorker().fetchResource(StructureDefinition.class, profile.getBaseDefinition(), profile); 1720 if (bsd != null) { 1721 if (bsd.hasWebPath()) { 1722 c.getPieces().add(gen.new Piece(Utilities.isAbsoluteUrl(bsd.getWebPath()) ? bsd.getWebPath() : imagePath +bsd.getWebPath(), bsd.getName(), null)); 1723 } else { 1724 c.getPieces().add(gen.new Piece(null, bsd.getName(), null)); 1725 } 1726 } 1727 return c; 1728 } else if (e.hasContentReference()) { 1729 return c; 1730 } else { 1731 ElementDefinition d = (ElementDefinition) e.getUserData(ProfileUtilities.UD_DERIVATION_POINTER); 1732 if (d != null && d.hasType()) { 1733 types = new ArrayList<ElementDefinition.TypeRefComponent>(); 1734 for (TypeRefComponent tr : d.getType()) { 1735 TypeRefComponent tt = tr.copy(); 1736 tt.setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, true); 1737 types.add(tt); 1738 } 1739 } else { 1740 return c; 1741 } 1742 } 1743 } 1744 1745 boolean first = true; 1746 1747 TypeRefComponent tl = null; 1748 for (TypeRefComponent t : types) { 1749 if (!mustSupportMode || allTypesMustSupport(e) || isMustSupport(t)) { 1750 if (first) { 1751 first = false; 1752 } else { 1753 c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null))); 1754 } 1755 tl = t; 1756 if (t.hasTarget()) { 1757 c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null)); 1758 if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) { 1759 c.addPiece(gen.new Piece(null, " ", null)); 1760 c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); 1761 } 1762 c.getPieces().add(gen.new Piece(null, "(", null)); 1763 boolean tfirst = true; 1764 for (CanonicalType u : t.getTargetProfile()) { 1765 if (!mustSupportMode || allProfilesMustSupport(t.getTargetProfile()) || isMustSupport(u)) { 1766 if (tfirst) 1767 tfirst = false; 1768 else 1769 c.addPiece(gen.new Piece(null, " | ", null)); 1770 genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue(), null); 1771 if (!mustSupportMode && isMustSupport(u) && e.getMustSupport()) { 1772 c.addPiece(gen.new Piece(null, " ", null)); 1773 c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false); 1774 } 1775 } 1776 } 1777 c.getPieces().add(gen.new Piece(null, ")", null)); 1778 if (t.getAggregation().size() > 0) { 1779 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null)); 1780 boolean firstA = true; 1781 for (Enumeration<AggregationMode> a : t.getAggregation()) { 1782 if (firstA == true) 1783 firstA = false; 1784 else 1785 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null)); 1786 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue()))); 1787 } 1788 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null)); 1789 } 1790 } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type 1791 String ref; 1792 boolean pfirst = true; 1793 for (CanonicalType p : t.getProfile()) { 1794 if (!mustSupportMode || allProfilesMustSupport(t.getProfile()) || isMustSupport(p)) { 1795 if (pfirst) { 1796 pfirst = false; 1797 } else { 1798 c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null))); 1799 } 1800 1801 ref = context.getPkp() == null ? null : context.getPkp().getLinkForProfile(profile, p.getValue()); 1802 if (ref != null) { 1803 String[] parts = ref.split("\\|"); 1804 if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) { 1805 if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) { 1806 String pp = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT); 1807 pp = pp.substring(pp.indexOf(".")); 1808 c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1]+pp, t.getWorkingCode()))); 1809 } else { 1810 c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode()))); 1811 } 1812 } else { 1813 c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getWorkingCode()))); 1814 } 1815 } else { 1816 c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null))); 1817 } 1818 if (!mustSupportMode && isMustSupport(p) && e.getMustSupport()) { 1819 c.addPiece(gen.new Piece(null, " ", null)); 1820 c.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false); 1821 } 1822 } 1823 } 1824 } else { 1825 String tc = t.getWorkingCode(); 1826 if (Utilities.isAbsoluteUrl(tc)) { 1827 StructureDefinition sd = context.getWorker().fetchTypeDefinition(tc); 1828 if (sd == null) { 1829 c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, tc), tc, null))); 1830 } else { 1831 c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, tc), sd.getTypeName(), null))); 1832 } 1833 } else if (context.getPkp() != null && context.getPkp().hasLinkFor(tc)) { 1834 c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, tc), tc, null))); 1835 } else { 1836 c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null))); 1837 } 1838 if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) { 1839 c.addPiece(gen.new Piece(null, " ", null)); 1840 c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); 1841 } 1842 } 1843 } 1844 } 1845 return c; 1846 } 1847 1848 1849 private String pfx(String prefix, String url) { 1850 return Utilities.isAbsoluteUrl(url) ? url : prefix + url; 1851 } 1852 1853 private void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, TypeRefComponent t, String u, Resource src) { 1854 if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 1855 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, u, src); 1856 if (sd != null) { 1857 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 1858 c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getWebPath()), disp, null))); 1859 } else { 1860 String rn = u.substring(40); 1861 c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, rn), rn, null))); 1862 } 1863 } else if (Utilities.isAbsoluteUrl(u)) { 1864 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, u, src); 1865 if (sd != null && context.getPkp() != null) { 1866 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 1867 String ref = context.getPkp().getLinkForProfile(null, sd.getUrl()); 1868 if (ref != null && ref.contains("|")) 1869 ref = ref.substring(0, ref.indexOf("|")); 1870 c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null))); 1871 } else 1872 c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null))); 1873 } else if (t.hasTargetProfile() && u.startsWith("#")) 1874 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.substring(1).toLowerCase()+".html", u, null))); 1875 } 1876 1877 private boolean isProfiledType(List<CanonicalType> theProfile) { 1878 for (CanonicalType next : theProfile){ 1879 if (StringUtils.defaultString(next.getValueAsString()).contains(":")) { 1880 return true; 1881 } 1882 } 1883 return false; 1884 } 1885 1886 1887 public String codeForAggregation(AggregationMode a) { 1888 switch (a) { 1889 case BUNDLED : return "b"; 1890 case CONTAINED : return "c"; 1891 case REFERENCED: return "r"; 1892 default: return "?"; 1893 } 1894 } 1895 1896 public String hintForAggregation(AggregationMode a) { 1897 if (a != null) 1898 return a.getDefinition(); 1899 else 1900 return null; 1901 } 1902 1903 1904 private String checkPrepend(String corePath, String path) { 1905 if (context.getPkp() != null && context.getPkp().prependLinks() && !(path.startsWith("http:") || path.startsWith("https:"))) 1906 return corePath+path; 1907 else 1908 return path; 1909 } 1910 1911 1912 private ElementDefinition findParent(List<ElementDefinition> list, int i, String path) { 1913 while (i > 0 && !path.startsWith(list.get(i).getPath()+".")) { 1914 i--; 1915 } 1916 return list.get(i); 1917 } 1918 1919 private boolean isSibling(String[] pathCurrent, String[] pathLast, int firstDiff) { 1920 return pathCurrent.length == pathLast.length && firstDiff == pathCurrent.length-1; 1921 } 1922 1923 1924 private boolean isChild(String[] pathCurrent, String[] pathLast, int firstDiff) { 1925 return pathCurrent.length == pathLast.length+1 && firstDiff == pathLast.length; 1926 } 1927 1928 private String makeTail(String[] pathCurrent, int start, int index) { 1929 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("."); 1930 for (int i = start; i <= index; i++) { 1931 b.append(pathCurrent[i]); 1932 } 1933 return b.toString(); 1934 } 1935 1936 private void genGridElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, String corePath, String imagePath, boolean root, boolean isConstraintMode) throws IOException, FHIRException { 1937 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 1938 String s = tail(element.getPath()); 1939 List<ElementDefinition> children = getChildren(all, element); 1940 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 1941 1942 if (!onlyInformationIsMapping(all, element)) { 1943 Row row = gen.new Row(); 1944 row.setAnchor(element.getPath()); 1945 row.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode)); 1946 if (element.hasSlicing()) 1947 row.setLineColor(1); 1948 else if (element.hasSliceName()) 1949 row.setLineColor(2); 1950 else 1951 row.setLineColor(0); 1952 boolean hasDef = element != null; 1953 String ref = defPath == null ? null : defPath + element.getId(); 1954 UnusedTracker used = new UnusedTracker(); 1955 used.used = true; 1956 Cell left = gen.new Cell(); 1957 if (element.getType().size() == 1 && element.getType().get(0).isPrimitive()) 1958 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold")); 1959 else 1960 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement()))); 1961 if (element.hasSliceName()) { 1962 left.getPieces().add(gen.new Piece("br")); 1963 String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length)); 1964 left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null)); 1965 } 1966 row.getCells().add(left); 1967 1968 genCardinality(gen, element, row, hasDef, used, null); 1969 if (hasDef && !"0".equals(element.getMax())) 1970 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, false); 1971 else 1972 row.getCells().add(gen.new Cell()); 1973 generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null); 1974 /* if (element.hasSlicing()) { 1975 if (standardExtensionSlicing(element)) { 1976 used.used = element.hasType() && element.getType().get(0).hasProfile(); 1977 showMissing = false; 1978 } else { 1979 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 1980 row.getCells().get(2).getPieces().clear(); 1981 for (Cell cell : row.getCells()) 1982 for (Piece p : cell.getPieces()) { 1983 p.addStyle("font-style: italic"); 1984 } 1985 } 1986 }*/ 1987 rows.add(row); 1988 for (ElementDefinition child : children) 1989 if (child.getMustSupport()) 1990 genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode); 1991 } 1992 } 1993 1994 1995 private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value) { 1996 if (value.contains("#")) { 1997 StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 1998 if (ext == null) 1999 return null; 2000 String tail = value.substring(value.indexOf("#")+1); 2001 ElementDefinition ed = null; 2002 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 2003 if (tail.equals(ted.getSliceName())) { 2004 ed = ted; 2005 return new ExtensionContext(ext, ed); 2006 } 2007 } 2008 return null; 2009 } else { 2010 StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value); 2011 if (ext == null) 2012 return null; 2013 else 2014 return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0)); 2015 } 2016 } 2017 2018 2019 private boolean extensionIsComplex(String value) { 2020 if (value.contains("#")) { 2021 StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 2022 if (ext == null) 2023 return false; 2024 String tail = value.substring(value.indexOf("#")+1); 2025 ElementDefinition ed = null; 2026 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 2027 if (tail.equals(ted.getSliceName())) { 2028 ed = ted; 2029 break; 2030 } 2031 } 2032 if (ed == null) 2033 return false; 2034 int i = ext.getSnapshot().getElement().indexOf(ed); 2035 int j = i+1; 2036 while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath())) 2037 j++; 2038 return j - i > 5; 2039 } else { 2040 StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value); 2041 return ext != null && ext.getSnapshot().getElement().size() > 5; 2042 } 2043 } 2044 2045 2046 2047 2048 private BindingResolution makeNullBr(ElementDefinitionBindingComponent binding) { 2049 BindingResolution br = new BindingResolution(); 2050 br.url = "http://none.none/none"; 2051 br.display = "todo"; 2052 return br; 2053 } 2054 2055 private ElementDefinitionBindingComponent makeUnifiedBinding(ElementDefinitionBindingComponent binding, ElementDefinition element) { 2056 if (!element.hasUserData(ProfileUtilities.UD_DERIVATION_POINTER)) { 2057 return binding; 2058 } 2059 ElementDefinition base = (ElementDefinition) element.getUserData(ProfileUtilities.UD_DERIVATION_POINTER); 2060 if (!base.hasBinding()) { 2061 return binding; 2062 } 2063 ElementDefinitionBindingComponent o = base.getBinding(); 2064 ElementDefinitionBindingComponent b = new ElementDefinitionBindingComponent(); 2065 b.setUserData(ProfileUtilities.UD_DERIVATION_POINTER, o); 2066 if (binding.hasValueSet()) { 2067 b.setValueSet(binding.getValueSet()); 2068 } else if (o.hasValueSet()) { 2069 b.setValueSet(o.getValueSet()); 2070 b.getValueSetElement().setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, o.getValueSetElement()); 2071 } 2072 if (binding.hasStrength()) { 2073 b.setStrength(binding.getStrength()); 2074 } else if (o.hasStrength()) { 2075 b.setStrength(o.getStrength()); 2076 b.getStrengthElement().setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, o.getStrengthElement()); 2077 } 2078 if (binding.hasDescription()) { 2079 b.setDescription(binding.getDescription()); 2080 } else if (o.hasDescription()) { 2081 b.setDescription(o.getDescription()); 2082 b.getDescriptionElement().setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, o.getDescriptionElement()); 2083 } 2084 // todo: derivation? 2085 b.getExtension().addAll(binding.getExtension()); 2086 return b; 2087 } 2088 2089 private void genFixedValue(HierarchicalTableGenerator gen, Row erow, DataType value, boolean snapshot, boolean pattern, String corePath, boolean skipnoValue) { 2090 String ref = context.getPkp().getLinkFor(corePath, value.fhirType()); 2091 if (ref != null && ref.contains(".html")) { 2092 ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#"; 2093 } else { 2094 ref = "?gen-fv?"; 2095 } 2096 StructureDefinition sd = context.getWorker().fetchTypeDefinition(value.fhirType()); 2097 2098 for (org.hl7.fhir.r5.model.Property t : value.children()) { 2099 if (t.getValues().size() > 0 || snapshot) { 2100 ElementDefinition ed = findElementDefinition(sd, t.getName()); 2101 if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) { 2102 if (!skipnoValue) { 2103 Row row = gen.new Row(); 2104 erow.getSubRows().add(row); 2105 Cell c = gen.new Cell(); 2106 row.getCells().add(c); 2107 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+(VersionUtilities.isR5Plus(context.getWorker().getVersion()) ? "types-definitions.html#"+ed.getBase().getPath() : "element-definitions.html#"+ed.getBase().getPath())), t.getName(), null)); 2108 c = gen.new Cell(); 2109 row.getCells().add(c); 2110 c.addPiece(gen.new Piece(null, null, null)); 2111 c = gen.new Cell(); 2112 row.getCells().add(c); 2113 if (!pattern) { 2114 c.addPiece(gen.new Piece(null, "0..0", null)); 2115 row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/); 2116 } else if (isPrimitive(t.getTypeCode())) { 2117 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 2118 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 2119 } else if (isReference(t.getTypeCode())) { 2120 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2121 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 2122 } else { 2123 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2124 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 2125 } 2126 c = gen.new Cell(); 2127 row.getCells().add(c); 2128 if (t.getTypeCode().contains("(")) { 2129 String tc = t.getTypeCode(); 2130 String tn = tc.substring(0, tc.indexOf("(")); 2131 c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, tn), tn, null)); 2132 c.addPiece(gen.new Piece(null, "(", null)); 2133 String[] p = tc.substring(tc.indexOf("(")+1, tc.indexOf(")")).split("\\|"); 2134 for (String s : p) { 2135 c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, s), s, null)); 2136 } 2137 c.addPiece(gen.new Piece(null, ")", null)); 2138 } else { 2139 c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null)); 2140 } 2141 c = gen.new Cell(); 2142 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 2143 row.getCells().add(c); 2144 } 2145 } else { 2146 for (Base b : t.getValues()) { 2147 Row row = gen.new Row(); 2148 erow.getSubRows().add(row); 2149 row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/); 2150 2151 Cell c = gen.new Cell(); 2152 row.getCells().add(c); 2153 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : (VersionUtilities.isR5Ver(context.getWorker().getVersion()) ? corePath+"types-definitions.html#"+ed.getBase().getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath())), t.getName(), null)); 2154 2155 c = gen.new Cell(); 2156 row.getCells().add(c); 2157 c.addPiece(gen.new Piece(null, null, null)); 2158 2159 c = gen.new Cell(); 2160 row.getCells().add(c); 2161 if (pattern) 2162 c.addPiece(gen.new Piece(null, "1.."+(t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 2163 else 2164 c.addPiece(gen.new Piece(null, "1..1", null)); 2165 2166 c = gen.new Cell(); 2167 row.getCells().add(c); 2168 if (b.fhirType().contains("(")) { 2169 String tc = b.fhirType(); 2170 String tn = tc.substring(0, tc.indexOf("(")); 2171 c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, tn), tn, null)); 2172 c.addPiece(gen.new Piece(null, "(", null)); 2173 String[] p = tc.substring(tc.indexOf("(")+1, tc.indexOf(")")).split("\\|"); 2174 for (String s : p) { 2175 c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, s), s, null)); 2176 } 2177 c.addPiece(gen.new Piece(null, ")", null)); 2178 } else { 2179 c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, b.fhirType()), b.fhirType(), null)); 2180 } 2181 2182 if (b.isPrimitive()) { 2183 c = gen.new Cell(); 2184 row.getCells().add(c); 2185 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 2186 c.addPiece(gen.new Piece("br")); 2187 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 2188 String s = b.primitiveValue(); 2189 // ok. let's see if we can find a relevant link for this 2190 String link = null; 2191 if (Utilities.isAbsoluteUrl(s)) { 2192 link = context.getPkp().getLinkForUrl(corePath, s); 2193 } 2194 c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen")); 2195 } else { 2196 c = gen.new Cell(); 2197 row.getCells().add(c); 2198 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 2199 c.addPiece(gen.new Piece("br")); 2200 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 2201 c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen")); 2202 genFixedValue(gen, row, (DataType) b, snapshot, pattern, corePath, skipnoValue); 2203 } 2204 } 2205 } 2206 } 2207 } 2208 } 2209 2210 2211 private ElementDefinition findElementDefinition(StructureDefinition sd, String name) { 2212 String path = sd.getTypeName()+"."+name; 2213 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2214 if (ed.getPath().equals(path)) 2215 return ed; 2216 } 2217 throw new FHIRException(context.getWorker().formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT_, path)); 2218 } 2219 2220 2221 private String getFixedUrl(StructureDefinition sd) { 2222 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2223 if (ed.getPath().equals("Extension.url")) { 2224 if (ed.hasFixed() && ed.getFixed() instanceof UriType) 2225 return ed.getFixed().primitiveValue(); 2226 } 2227 } 2228 return null; 2229 } 2230 2231 2232 private Piece describeCoded(HierarchicalTableGenerator gen, DataType fixed) { 2233 if (fixed instanceof Coding) { 2234 Coding c = (Coding) fixed; 2235 ValidationResult vr = context.getWorker().validateCode(context.getTerminologyServiceOptions(), c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay()); 2236 if (vr.getDisplay() != null) 2237 return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); 2238 } else if (fixed instanceof CodeableConcept) { 2239 CodeableConcept cc = (CodeableConcept) fixed; 2240 for (Coding c : cc.getCoding()) { 2241 ValidationResult vr = context.getWorker().validateCode(context.getTerminologyServiceOptions(), c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay()); 2242 if (vr.getDisplay() != null) 2243 return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); 2244 } 2245 } 2246 return null; 2247 } 2248 2249 2250 private boolean hasDescription(DataType fixed) { 2251 if (fixed instanceof Coding) { 2252 return ((Coding) fixed).hasDisplay(); 2253 } else if (fixed instanceof CodeableConcept) { 2254 CodeableConcept cc = (CodeableConcept) fixed; 2255 if (cc.hasText()) 2256 return true; 2257 for (Coding c : cc.getCoding()) 2258 if (c.hasDisplay()) 2259 return true; 2260 } // (fixed instanceof CodeType) || (fixed instanceof Quantity); 2261 return false; 2262 } 2263 2264 2265 private boolean isCoded(DataType fixed) { 2266 return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity); 2267 } 2268 2269 2270 private Cell generateGridDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, ElementDefinition valueDefn) throws IOException, FHIRException { 2271 Cell c = gen.new Cell(); 2272 row.getCells().add(c); 2273 2274 if (used) { 2275 if (definition.hasContentReference()) { 2276 ElementInStructure ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference(), profile); 2277 if (ed == null) 2278 c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null)); 2279 else { 2280 if (ed.getSource() == profile) { 2281 c.getPieces().add(gen.new Piece("#"+ed.getElement().getPath(), "See "+ed.getElement().getPath(), null)); 2282 } else { 2283 c.getPieces().add(gen.new Piece(ed.getSource().getWebPath()+"#"+ed.getElement().getPath(), "See "+ed.getSource().getTypeName()+"."+ed.getElement().getPath(), null)); 2284 } 2285 } 2286 } 2287 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 2288 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 2289 } else { 2290 if (url != null) { 2291 if (!c.getPieces().isEmpty()) 2292 c.addPiece(gen.new Piece("br")); 2293 String fullUrl = url.startsWith("#") ? baseURL+url : url; 2294 StructureDefinition ed = context.getWorker().fetchResource(StructureDefinition.class, url, profile); 2295 String ref = null; 2296 if (ed != null) { 2297 String p = ed.getWebPath(); 2298 if (p != null) { 2299 ref = p.startsWith("http:") || context.getRules() == GenerationRules.IG_PUBLISHER ? p : Utilities.pathURL(corePath, p); 2300 } 2301 } 2302 c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold")); 2303 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 2304 } 2305 2306 if (definition.hasSlicing()) { 2307 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 2308 c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); 2309 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 2310 } 2311 if (definition != null) { 2312 ElementDefinitionBindingComponent binding = null; 2313 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 2314 binding = valueDefn.getBinding(); 2315 else if (definition.hasBinding()) 2316 binding = definition.getBinding(); 2317 if (binding!=null && !binding.isEmpty()) { 2318 if (!c.getPieces().isEmpty()) 2319 c.addPiece(gen.new Piece("br")); 2320 BindingResolution br = context.getPkp().resolveBinding(profile, binding, definition.getPath()); 2321 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold"))); 2322 c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, null))); 2323 if (binding.hasStrength()) { 2324 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 2325 c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), binding.getStrength().toCode(), binding.getStrength().getDefinition()))); c.getPieces().add(gen.new Piece(null, ")", null)); 2326 } 2327 if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) { 2328 c.getPieces().add(gen.new Piece(null, ": ", null)); 2329 c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement()).asStringValue()); 2330 } 2331 } 2332 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 2333 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 2334 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 2335 if (inv.getHumanElement().hasExtension(ToolingExtensions.EXT_REND_MD)) { 2336 c.addMarkdown(inv.getHumanElement().getExtensionString(ToolingExtensions.EXT_REND_MD)); 2337 } else { 2338 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); 2339 } 2340 } 2341 if (definition.hasFixed()) { 2342 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 2343 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); 2344 String s = buildJson(definition.getFixed()); 2345 String link = null; 2346 if (Utilities.isAbsoluteUrl(s)) 2347 link = context.getPkp().getLinkForUrl(corePath, s); 2348 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 2349 } else if (definition.hasPattern()) { 2350 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 2351 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); 2352 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 2353 } else if (definition.hasExample()) { 2354 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 2355 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 2356 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "": " "+ex.getLabel()+"'")+": ", "").addStyle("font-weight:bold"))); 2357 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 2358 } 2359 } 2360 if (definition.hasMaxLength() && definition.getMaxLength()!=0) { 2361 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 2362 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 2363 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 2364 } 2365 if (profile != null) { 2366 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 2367 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 2368 ElementDefinitionMappingComponent map = null; 2369 for (ElementDefinitionMappingComponent m : definition.getMapping()) 2370 if (m.getIdentity().equals(md.getIdentity())) 2371 map = m; 2372 if (map != null) { 2373 for (int i = 0; i<definition.getMapping().size(); i++){ 2374 c.addPiece(gen.new Piece("br")); 2375 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null)); 2376 } 2377 } 2378 } 2379 } 2380 } 2381 if (definition.hasDefinition()) { 2382 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 2383 c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold")); 2384 c.addPiece(gen.new Piece("br")); 2385 c.addMarkdown(definition.getDefinition()); 2386 // c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 2387 } 2388 if (definition.getComment()!=null) { 2389 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 2390 c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold")); 2391 c.addPiece(gen.new Piece("br")); 2392 c.addMarkdown(definition.getComment()); 2393 // c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 2394 } 2395 } 2396 } 2397 } 2398 return c; 2399 } 2400 2401 private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) { 2402 return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) && 2403 getChildren(list, e).isEmpty(); 2404 } 2405 2406 private boolean onlyInformationIsMapping(ElementDefinition d) { 2407 return !d.hasShort() && !d.hasDefinition() && 2408 !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() && 2409 !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() && 2410 !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() && 2411 !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() && 2412 !d.hasBinding(); 2413 } 2414 2415 private boolean allAreReference(List<TypeRefComponent> types) { 2416 for (TypeRefComponent t : types) { 2417 if (!t.hasTarget()) 2418 return false; 2419 } 2420 return true; 2421 } 2422 2423 private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) { 2424 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 2425 int i = all.indexOf(element)+1; 2426 while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) { 2427 if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains(".")) 2428 result.add(all.get(i)); 2429 i++; 2430 } 2431 return result; 2432 } 2433 2434 2435 protected String tail(String path) { 2436 if (path == null) { 2437 return ""; 2438 } else if (path.contains(".")) 2439 return path.substring(path.lastIndexOf('.')+1); 2440 else 2441 return path; 2442 } 2443 2444 2445 2446 2447 2448 protected boolean isPrimitive(String value) { 2449 StructureDefinition sd = context.getWorker().fetchTypeDefinition(value); 2450 if (sd == null) // might be running before all SDs are available 2451 return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid"); 2452 else 2453 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 2454 } 2455 2456 2457 private boolean isDataType(String value) { 2458 StructureDefinition sd = context.getWorker().fetchTypeDefinition(value); 2459 if (sd == null) // might be running before all SDs are available 2460 return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", 2461 "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext"); 2462 else 2463 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 2464 } 2465 2466 private boolean slicesExist(List<ElementDefinition> elements, ElementDefinition element) { 2467 if (elements == null) { 2468 return true; 2469 } 2470 boolean found = false; 2471 int start = elements.indexOf(element); 2472 if (start < 0) { 2473 return false; 2474 } 2475 for (int i = start; i < elements.size(); i++) { 2476 ElementDefinition ed = elements.get(i); 2477 if (ed.getPath().equals(element.getPath())) { 2478 if (ed.hasSliceName()) { 2479 found = true; 2480 } 2481 } 2482 if (ed.getPath().length() < element.getPath().length()) { 2483 break; 2484 } 2485 } 2486 return found; 2487 } 2488 2489 2490 private Cell addCell(Row row, Cell cell) { 2491 row.getCells().add(cell); 2492 return (cell); 2493 } 2494 2495 private String checkAdd(String src, String app) { 2496 return app == null ? src : src + app; 2497 } 2498 2499 public boolean hasNonBaseConditions(List<IdType> conditions) { 2500 for (IdType c : conditions) { 2501 if (!isBaseCondition(c)) { 2502 return true; 2503 } 2504 } 2505 return false; 2506 } 2507 2508 2509 public boolean hasNonBaseConstraints(List<ElementDefinitionConstraintComponent> constraints) { 2510 for (ElementDefinitionConstraintComponent c : constraints) { 2511 if (!isBaseConstraint(c)) { 2512 return true; 2513 } 2514 } 2515 return false; 2516 } 2517 2518 public String listConstraintsAndConditions(ElementDefinition element) { 2519 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2520 for (ElementDefinitionConstraintComponent con : element.getConstraint()) { 2521 if (!isBaseConstraint(con)) { 2522 b.append(con.getKey()); 2523 } 2524 } 2525 for (IdType id : element.getCondition()) { 2526 if (!isBaseCondition(id)) { 2527 b.append(id.asStringValue()); 2528 } 2529 } 2530 return b.toString(); 2531 } 2532 2533 private boolean isBaseCondition(IdType c) { 2534 String key = c.asStringValue(); 2535 return key != null && (key.startsWith("ele-") || key.startsWith("res-") || key.startsWith("ext-") || key.startsWith("dom-") || key.startsWith("dr-")); 2536 } 2537 2538 private boolean isBaseConstraint(ElementDefinitionConstraintComponent con) { 2539 String key = con.getKey(); 2540 return key != null && (key.startsWith("ele-") || key.startsWith("res-") || key.startsWith("ext-") || key.startsWith("dom-") || key.startsWith("dr-")); 2541 } 2542 2543 private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, String corePath, String profileBaseFileName, boolean mustSupportMode, Resource src) { 2544 // create a child for each choice 2545 for (TypeRefComponent tr : element.getType()) { 2546 if (!mustSupportMode || allTypesMustSupport(element) || isMustSupport(tr)) { 2547 Row choicerow = gen.new Row(); 2548 String t = tr.getWorkingCode(); 2549 if (isReference(t)) { 2550 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null)); 2551 choicerow.getCells().add(gen.new Cell()); 2552 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2553 choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2554 Cell c = gen.new Cell(); 2555 choicerow.getCells().add(c); 2556 if (ADD_REFERENCE_TO_TABLE) { 2557 if (tr.getWorkingCode().equals("canonical")) 2558 c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null)); 2559 else 2560 c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null)); 2561 if (!mustSupportMode && isMustSupportDirect(tr) && element.getMustSupport()) { 2562 c.addPiece(gen.new Piece(null, " ", null)); 2563 c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); 2564 } 2565 c.getPieces().add(gen.new Piece(null, "(", null)); 2566 } 2567 boolean first = true; 2568 for (CanonicalType rt : tr.getTargetProfile()) { 2569 if (!mustSupportMode || allProfilesMustSupport(tr.getTargetProfile()) || isMustSupport(rt)) { 2570 if (!first) 2571 c.getPieces().add(gen.new Piece(null, " | ", null)); 2572 genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue(), src); 2573 if (!mustSupportMode && isMustSupport(rt) && element.getMustSupport()) { 2574 c.addPiece(gen.new Piece(null, " ", null)); 2575 c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false); 2576 } 2577 first = false; 2578 } 2579 } 2580 if (first) { 2581 c.getPieces().add(gen.new Piece(null, "Any", null)); 2582 } 2583 2584 if (ADD_REFERENCE_TO_TABLE) { 2585 c.getPieces().add(gen.new Piece(null, ")", null)); 2586 } 2587 2588 } else { 2589 StructureDefinition sd = context.getWorker().fetchTypeDefinition(t); 2590 if (sd == null) { 2591 System.out.println("Unable to find "+t); 2592 sd = context.getWorker().fetchTypeDefinition(t); 2593 } else if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 2594 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 2595 choicerow.getCells().add(gen.new Cell()); 2596 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2597 choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 2598 Cell c = gen.new Cell(null, corePath+"datatypes.html#"+t, sd.getTypeName(), null, null); 2599 choicerow.getCells().add(c); 2600 if (!mustSupportMode && isMustSupport(tr) && element.getMustSupport()) { 2601 c.addPiece(gen.new Piece(null, " ", null)); 2602 c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); 2603 } 2604 } else { 2605 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 2606 choicerow.getCells().add(gen.new Cell()); 2607 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2608 choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2609 Cell c = gen.new Cell(null, context.getPkp().getLinkFor(corePath, t), sd.getTypeName(), null, null); 2610 choicerow.getCells().add(c); 2611 if (!mustSupportMode && isMustSupport(tr) && element.getMustSupport()) { 2612 c.addPiece(gen.new Piece(null, " ", null)); 2613 c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); 2614 } 2615 } 2616 if (tr.hasProfile()) { 2617 Cell typeCell = choicerow.getCells().get(3); 2618 typeCell.addPiece(gen.new Piece(null, "(", null)); 2619 boolean first = true; 2620 for (CanonicalType pt : tr.getProfile()) { 2621 if (!mustSupportMode || allProfilesMustSupport(tr.getProfile()) || isMustSupport(pt)) { 2622 if (first) first = false; else typeCell.addPiece(gen.new Piece(null, " | ", null)); 2623 StructureDefinition psd = context.getWorker().fetchResource(StructureDefinition.class, pt.getValue(), src); 2624 if (psd == null) 2625 typeCell.addPiece(gen.new Piece(null, "?gen-e2?", null)); 2626 else 2627 typeCell.addPiece(gen.new Piece(psd.getWebPath(), psd.getName(), psd.present())); 2628 if (!mustSupportMode && isMustSupport(pt) && element.getMustSupport()) { 2629 typeCell.addPiece(gen.new Piece(null, " ", null)); 2630 typeCell.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false); 2631 } 2632 } 2633 } 2634 typeCell.addPiece(gen.new Piece(null, ")", null)); 2635 } 2636 } 2637 choicerow.getCells().add(gen.new Cell()); 2638 subRows.add(choicerow); 2639 } 2640 } 2641 } 2642 2643 private boolean isReference(String t) { 2644 return t.equals("Reference") || t.equals("canonical"); 2645 } 2646 2647 2648 2649 private List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) { 2650 List<ElementChoiceGroup> result = new ArrayList<>(); 2651 for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { 2652 ElementChoiceGroup grp = context.getProfileUtilities().processConstraint(children, c); 2653 if (grp != null) { 2654 result.add(grp); 2655 } 2656 } 2657 return result; 2658 } 2659 2660 private Piece checkForNoChange(Element src1, Element src2, Piece piece) { 2661 if (src1.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS) && src2.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS)) { 2662 piece.addStyle("opacity: 0.5"); 2663 } 2664 return piece; 2665 } 2666 2667 2668 private String buildJson(DataType value) throws IOException { 2669 if (value instanceof PrimitiveType) 2670 return ((PrimitiveType<?>) value).asStringValue(); 2671 2672 IParser json = new JsonParser(); 2673 return json.composeString(value, null); 2674 } 2675 2676 private String describeSlice(ElementDefinitionSlicingComponent slicing) { 2677 return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator())); 2678 } 2679 2680 2681 2682 private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) { 2683 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 2684 for (ElementDefinitionSlicingDiscriminatorComponent id : list) 2685 c.append((id.hasType() ? id.getType().toCode() : "??")+":"+id.getPath()); 2686 return c.toString(); 2687 } 2688 2689 2690 private String describe(SlicingRules rules) { 2691 if (rules == null) 2692 return translate("sd.table", "Unspecified"); 2693 switch (rules) { 2694 case CLOSED : return translate("sd.table", "Closed"); 2695 case OPEN : return translate("sd.table", "Open"); 2696 case OPENATEND : return translate("sd.table", "Open At End"); 2697 default: 2698 return "?gen-sr?"; 2699 } 2700 } 2701 2702 private boolean allTypesMustSupport(ElementDefinition e) { 2703 boolean all = true; 2704 boolean any = false; 2705 for (TypeRefComponent tr : e.getType()) { 2706 all = all && isMustSupport(tr); 2707 any = any || isMustSupport(tr); 2708 } 2709 return !all && !any; 2710 } 2711 2712 private boolean allProfilesMustSupport(List<CanonicalType> profiles) { 2713 boolean all = true; 2714 boolean any = false; 2715 for (CanonicalType u : profiles) { 2716 all = all && isMustSupport(u); 2717 any = any || isMustSupport(u); 2718 } 2719 return !all && !any; 2720 } 2721 public boolean isMustSupportDirect(TypeRefComponent tr) { 2722 return ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))); 2723 } 2724 2725 public boolean isMustSupport(TypeRefComponent tr) { 2726 if ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))) { 2727 return true; 2728 } 2729 if (isMustSupport(tr.getProfile())) { 2730 return true; 2731 } 2732 return isMustSupport(tr.getTargetProfile()); 2733 } 2734 2735 public boolean isMustSupport(List<CanonicalType> profiles) { 2736 for (CanonicalType ct : profiles) { 2737 if (isMustSupport(ct)) { 2738 return true; 2739 } 2740 } 2741 return false; 2742 } 2743 2744 2745 public boolean isMustSupport(CanonicalType profile) { 2746 return "true".equals(ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_MUST_SUPPORT)); 2747 } 2748 2749 2750 2751 private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException { 2752 SpanEntry res = new SpanEntry(); 2753 res.setName(name); 2754 res.setCardinality(cardinality); 2755 res.setProfileLink(profile.getWebPath()); 2756 res.setResType(profile.getTypeName()); 2757 StructureDefinition base = context.getWorker().fetchResource(StructureDefinition.class, res.getResType()); 2758 if (base != null) 2759 res.setResLink(base.getWebPath()); 2760 res.setId(profile.getId()); 2761 res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT); 2762 StringBuilder b = new StringBuilder(); 2763 b.append(res.getResType()); 2764 boolean first = true; 2765 boolean open = false; 2766 if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 2767 res.setDescription(profile.getName()); 2768 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 2769 if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) { 2770 if (first) { 2771 open = true; 2772 first = false; 2773 b.append("["); 2774 } else { 2775 b.append(", "); 2776 } 2777 b.append(tail(ed.getBase().getPath())); 2778 b.append("="); 2779 b.append(summarize(ed.getFixed())); 2780 } 2781 } 2782 if (open) 2783 b.append("]"); 2784 } else 2785 res.setDescription("Base FHIR "+profile.getName()); 2786 res.setType(b.toString()); 2787 return res ; 2788 } 2789 2790 2791 private String summarize(DataType value) throws IOException { 2792 if (value instanceof Coding) 2793 return summarizeCoding((Coding) value); 2794 else if (value instanceof CodeableConcept) 2795 return summarizeCodeableConcept((CodeableConcept) value); 2796 else 2797 return buildJson(value); 2798 } 2799 2800 2801 private String summarizeCoding(Coding value) { 2802 String uri = value.getSystem(); 2803 String system = TerminologyRenderer.describeSystem(uri); 2804 if (Utilities.isURL(system)) { 2805 if (system.equals("http://cap.org/protocols")) 2806 system = "CAP Code"; 2807 } 2808 return system+" "+value.getCode(); 2809 } 2810 2811 2812 private String summarizeCodeableConcept(CodeableConcept value) { 2813 if (value.hasCoding()) 2814 return summarizeCoding(value.getCodingFirstRep()); 2815 else 2816 return value.getText(); 2817 } 2818 2819 2820 private boolean isKeyProperty(String path) { 2821 return Utilities.existsInList(path, "Observation.code"); 2822 } 2823 2824 2825 private TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) throws IOException { 2826 TableModel model = gen.new TableModel(id, true); 2827 2828 if (context.getRules() == GenerationRules.VALID_RESOURCE || context.isInlineGraphics()) { 2829 model.setDocoImg(HierarchicalTableGenerator.help16AsData()); 2830 } else { 2831 model.setDocoImg(Utilities.pathURL(prefix, "help16.png")); 2832 } 2833 model.setDocoRef(Utilities.pathURL(prefix, "formats.html#table")); // todo: change to graph definition 2834 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0)); 2835 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Card.", "Minimum and Maximum # of times the the element can appear in the instance", null, 0)); 2836 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0)); 2837 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0)); 2838 return model; 2839 } 2840 2841 private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException { 2842 Row row = gen.new Row(); 2843 rows.add(row); 2844 row.setAnchor(span.getId()); 2845 //row.setColor(..?); 2846 if (span.isProfile()) { 2847 row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE); 2848 } else { 2849 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 2850 } 2851 2852 row.getCells().add(gen.new Cell(null, null, span.getName(), null, null)); 2853 row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null)); 2854 row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null)); 2855 row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null)); 2856 2857 for (SpanEntry child : span.getChildren()) { 2858 genSpanEntry(gen, row.getSubRows(), child); 2859 } 2860 } 2861 2862 2863 public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException { 2864 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true); 2865 gen.setTranslator(getTranslator()); 2866 TableModel model = initSpanningTable(gen, "", false, profile.getId()); 2867 Set<String> processed = new HashSet<String>(); 2868 SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix); 2869 2870 genSpanEntry(gen, model.getRows(), span); 2871 return gen.generate(model, "", 0, outputTracker); 2872 } 2873 2874 private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException { 2875 SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile); 2876 boolean wantProcess = !processed.contains(profile.getUrl()); 2877 processed.add(profile.getUrl()); 2878 if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 2879 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 2880 if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) { 2881 String card = getCardinality(ed, profile.getSnapshot().getElement()); 2882 if (!card.endsWith(".0")) { 2883 List<String> refProfiles = listReferenceProfiles(ed); 2884 if (refProfiles.size() > 0) { 2885 String uri = refProfiles.get(0); 2886 if (uri != null) { 2887 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, uri); 2888 if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) { 2889 res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix)); 2890 } 2891 } 2892 } 2893 } 2894 } 2895 } 2896 } 2897 return res; 2898 } 2899 2900 2901 private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) { 2902 int min = ed.getMin(); 2903 int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax()); 2904 ElementDefinition ned = ed; 2905 while (ned != null && ned.getPath().contains(".")) { 2906 ned = findParent(ned, list); 2907 if (ned != null) { // todo: this can happen if we've walked into a resoruce. Not sure what to about that? 2908 if ("0".equals(ned.getMax())) 2909 max = 0; 2910 else if (!ned.getMax().equals("1") && !ned.hasSlicing()) 2911 max = Integer.MAX_VALUE; 2912 if (ned.getMin() == 0) { 2913 min = 0; 2914 } 2915 } 2916 } 2917 return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max)); 2918 } 2919 2920 2921 private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) { 2922 int i = list.indexOf(ed)-1; 2923 while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+".")) 2924 i--; 2925 if (i == -1) 2926 return null; 2927 else 2928 return list.get(i); 2929 } 2930 2931 2932 private List<String> listReferenceProfiles(ElementDefinition ed) { 2933 List<String> res = new ArrayList<String>(); 2934 for (TypeRefComponent tr : ed.getType()) { 2935 // code is null if we're dealing with "value" and profile is null if we just have Reference() 2936 if (tr.hasTarget() && tr.hasTargetProfile()) 2937 for (UriType u : tr.getTargetProfile()) 2938 res.add(u.getValue()); 2939 } 2940 return res; 2941 } 2942 2943 2944 private String nameForElement(ElementDefinition ed) { 2945 return ed.getPath().substring(ed.getPath().indexOf(".")+1); 2946 } 2947 2948 public XhtmlNode formatTypeSpecifiers(ElementDefinition d) { 2949 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 2950 boolean first = true; 2951 for (Extension e : d.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) { 2952 if (first) first = false; else x.br(); 2953 String cond = ToolingExtensions.readStringExtension(e, "condition"); 2954 String type = ToolingExtensions.readStringExtension(e, "type"); 2955 x.tx("If "); 2956 x.code().tx(cond); 2957 x.tx(" then the type is "); 2958 StructureDefinition sd = context.getContext().fetchTypeDefinition(type); 2959 if (sd == null) { 2960 x.code().tx(type); 2961 } else { 2962 x.ah(sd.getWebPath()).tx(sd.getTypeName()); 2963 } 2964 } 2965 return first ? null : x; 2966 } 2967 2968 public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker, RenderingContext rc) throws IOException, FHIRException { 2969 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 2970 gen.setTranslator(getTranslator()); 2971 TableModel model = gen.initNormalTable(corePath, false, true, ed.getId()+(full ? "f" : "n"), true, TableGenerationMode.XHTML); 2972 2973 boolean deep = false; 2974 String m = ""; 2975 boolean vdeep = false; 2976 if (ed.getSnapshot().getElementFirstRep().getIsModifier()) 2977 m = "modifier_"; 2978 for (ElementDefinition eld : ed.getSnapshot().getElement()) { 2979 deep = deep || eld.getPath().contains("Extension.extension."); 2980 vdeep = vdeep || eld.getPath().contains("Extension.extension.extension."); 2981 } 2982 Row r = gen.new Row(); 2983 model.getRows().add(r); 2984 String en; 2985 if (!full) 2986 en = ed.getName(); 2987 else if (ed.getSnapshot().getElement().get(0).getIsModifier()) 2988 en = "modifierExtension"; 2989 else 2990 en = "extension"; 2991 2992 r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null)); 2993 r.getCells().add(gen.new Cell()); 2994 r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null)); 2995 2996 ElementDefinition ved = null; 2997 if (full || vdeep) { 2998 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 2999 3000 r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 3001 List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0)); 3002 for (ElementDefinition child : children) 3003 if (!child.getPath().endsWith(".id")) { 3004 List<StructureDefinition> sdl = new ArrayList<>(); 3005 sdl.add(ed); 3006 genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), sdl, true, defFile, true, full, corePath, imagePath, true, false, false, false, null, false, rc, "", ed, null); 3007 } 3008 } else if (deep) { 3009 List<ElementDefinition> children = new ArrayList<ElementDefinition>(); 3010 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 3011 if (ted.getPath().equals("Extension.extension")) 3012 children.add(ted); 3013 } 3014 3015 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 3016 r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 3017 3018 for (ElementDefinition c : children) { 3019 ved = getValueFor(ed, c); 3020 ElementDefinition ued = getUrlFor(ed, c); 3021 if (ved != null && ued != null) { 3022 Row r1 = gen.new Row(); 3023 r.getSubRows().add(r1); 3024 r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#"+ed.getId()+"."+c.getId(), ((UriType) ued.getFixed()).getValue(), null, null)); 3025 r1.getCells().add(gen.new Cell()); 3026 r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null)); 3027 genTypes(gen, r1, ved, defFile, ed, corePath, imagePath, false, false); 3028 r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 3029 generateDescription(gen, r1, c, null, true, corePath, corePath, ed, corePath, imagePath, false, false, false, ved, false, false, false, rc); 3030 } 3031 } 3032 } else { 3033 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 3034 if (ted.getPath().startsWith("Extension.value")) 3035 ved = ted; 3036 } 3037 3038 genTypes(gen, r, ved, defFile, ed, corePath, imagePath, false, false); 3039 3040 r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 3041 } 3042 Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null); 3043 Piece cc = gen.new Piece(null, ed.getName()+": ", null); 3044 c.addPiece(gen.new Piece("br")).addPiece(cc); 3045 c.addMarkdown(ed.getDescription()); 3046 3047 if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) { 3048 c.addPiece(gen.new Piece("br")); 3049 BindingResolution br = context.getPkp().resolveBinding(ed, ved.getBinding(), ved.getPath()); 3050 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"))); 3051 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, null))); 3052 if (ved.getBinding().hasStrength()) { 3053 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null))); 3054 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition()))); 3055 c.getPieces().add(gen.new Piece(null, ")", null)); 3056 } 3057 if (ved.getBinding().hasDescription() && MarkDownProcessor.isSimpleMarkdown(ved.getBinding().getDescription())) { 3058 c.getPieces().add(gen.new Piece(null, ": ", null)); 3059 c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context.getWorker(), ved.getBinding().getDescriptionElement()).asStringValue()); 3060 } 3061 } 3062 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, ProfileUtilities.describeExtensionContext(ed), null)); 3063 r.getCells().add(c); 3064 3065 try { 3066 return gen.generate(model, corePath, 0, outputTracker); 3067 } catch (org.hl7.fhir.exceptions.FHIRException e) { 3068 throw new FHIRException(e.getMessage(), e); 3069 } 3070 } 3071 3072 private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) { 3073 IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 3074 StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 3075 if (min.isEmpty() && fallback != null) 3076 min = fallback.getMinElement(); 3077 if (max.isEmpty() && fallback != null) 3078 max = fallback.getMaxElement(); 3079 3080 tracker.used = !max.isEmpty() && !max.getValue().equals("0"); 3081 3082 if (min.isEmpty() && max.isEmpty()) 3083 return null; 3084 else 3085 return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue()); 3086 } 3087 3088 3089 private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) { 3090 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 3091 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 3092 if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value")) 3093 return ed.getSnapshot().getElement().get(i); 3094 i++; 3095 } 3096 return null; 3097 } 3098 3099 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 3100 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 3101 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 3102 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url")) 3103 return ed.getSnapshot().getElement().get(i); 3104 i++; 3105 } 3106 return null; 3107 } 3108 3109 public void renderDict(StructureDefinition sd, List<ElementDefinition> elements, XhtmlNode t, boolean incProfiledOut, int mode, String anchorPrefix) throws FHIRException, IOException { 3110 int i = 0; 3111 Map<String, ElementDefinition> allAnchors = new HashMap<>(); 3112 List<ElementDefinition> excluded = new ArrayList<>(); 3113 List<ElementDefinition> stack = new ArrayList<>(); // keeps track of parents, for anchor generation 3114 3115 for (ElementDefinition ec : elements) { 3116 addToStack(stack, ec); 3117 generateAnchors(stack, allAnchors); 3118 checkInScope(stack, excluded); 3119 } 3120 Stack<ElementDefinition> dstack = new Stack<>(); 3121 for (ElementDefinition ec : elements) { 3122 if ((incProfiledOut || !"0".equals(ec.getMax())) && !excluded.contains(ec)) { 3123 ElementDefinition compareElement = null; 3124 if (mode==GEN_MODE_DIFF) 3125 compareElement = getBaseElement(ec, sd.getBaseDefinition()); 3126 else if (mode==GEN_MODE_KEY) 3127 compareElement = getRootElement(ec); 3128 3129 List<String> anchors = makeAnchors(ec, anchorPrefix); 3130 String title = ec.getId(); 3131 XhtmlNode tr = t.tr(); 3132 XhtmlNode sp = renderStatus(ec, tr.td("structure").colspan(2).spanClss("self-link-parent")); 3133 for (String s : anchors) { 3134 sp.an(s).tx(" "); 3135 } 3136 sp.span("color: grey", null).tx(Integer.toString(i++)); 3137 sp.b().tx(". "+title); 3138 link(sp, ec.getId(), anchorPrefix); 3139 if (isProfiledExtension(ec)) { 3140 StructureDefinition extDefn = context.getContext().fetchResource(StructureDefinition.class, ec.getType().get(0).getProfile().get(0).getValue()); 3141 if (extDefn == null) { 3142 generateElementInner(t, sd, ec, 1, null, compareElement, null, false); 3143 } else { 3144 ElementDefinition valueDefn = getExtensionValueDefinition(extDefn); 3145 ElementDefinition compareValueDefn = null; 3146 try { 3147 StructureDefinition compareExtDefn = context.getContext().fetchResource(StructureDefinition.class, compareElement.getType().get(0).getProfile().get(0).getValue()); 3148 compareValueDefn = getExtensionValueDefinition(extDefn); 3149 } catch (Exception except) {} 3150 generateElementInner(t, sd, ec, valueDefn == null || valueDefn.prohibited() ? 2 : 3, valueDefn, compareElement, compareValueDefn, false); 3151 // generateElementInner(b, extDefn, extDefn.getSnapshot().getElement().get(0), valueDefn == null ? 2 : 3, valueDefn); 3152 } 3153 } else { 3154 while (!dstack.isEmpty() && !isParent(dstack.peek(), ec)) { 3155 finish(t, sd, dstack.pop(), mode); 3156 } 3157 dstack.push(ec); 3158 generateElementInner(t, sd, ec, mode, null, compareElement, null, false); 3159 if (ec.hasSlicing()) { 3160 generateSlicing(t, sd, ec, ec.getSlicing(), compareElement, mode, false); 3161 } 3162 } 3163 } 3164 t.tx("\r\n"); 3165 i++; 3166 } 3167 while (!dstack.isEmpty()) { 3168 finish(t, sd, dstack.pop(), mode); 3169 } 3170 finish(t, sd, null, mode); 3171 } 3172 3173 private void finish(XhtmlNode t, StructureDefinition sd, ElementDefinition ed, int mode) throws FHIRException, IOException { 3174 3175 for (Base b : VersionComparisonAnnotation.getDeleted(ed == null ? sd : ed, "element")) { 3176 ElementDefinition ec = (ElementDefinition) b; 3177 String title = ec.getId(); 3178 XhtmlNode tr = t.tr(); 3179 XhtmlNode sp = renderStatus(ec, tr.td("structure").colspan(2).spanClss("self-link-parent")); 3180 sp.span("color: grey", null).tx("--"); 3181 sp.b().tx(". "+title); 3182 3183 generateElementInner(t, sd, ec, mode, null, null, null, true); 3184 if (ec.hasSlicing()) { 3185 generateSlicing(t, sd, ec, ec.getSlicing(), null, mode, true); 3186 } 3187 } 3188 } 3189 3190 public ElementDefinition getElementById(String url, String id) { 3191 Map<String, ElementDefinition> sdCache = sdMapCache.get(url); 3192 3193 if (sdCache == null) { 3194 StructureDefinition sd = (StructureDefinition) context.getContext().fetchResource(StructureDefinition.class, url); 3195 if (sd == null) { 3196 if (url.equals("http://hl7.org/fhir/StructureDefinition/Base")) { 3197 sd = (StructureDefinition) context.getContext().fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Element"); 3198 } 3199 if (sd == null) { 3200 throw new FHIRException("Unable to retrieve StructureDefinition with URL " + url); 3201 } 3202 } 3203 sdCache = new HashMap<String, ElementDefinition>(); 3204 sdMapCache.put(url, sdCache); 3205 String webroot = sd.getUserString("webroot"); 3206 for (ElementDefinition e : sd.getSnapshot().getElement()) { 3207 context.getProfileUtilities().updateURLs(sd.getUrl(), webroot, e); 3208 sdCache.put(e.getId(), e); 3209 } 3210 } 3211 return sdCache.get(id); 3212 } 3213 3214 3215 // Returns the ElementDefinition for the 'parent' of the current element 3216 private ElementDefinition getBaseElement(ElementDefinition e, String url) { 3217 if (e.hasUserData(ProfileUtilities.UD_DERIVATION_POINTER)) { 3218 return getElementById(url, e.getUserString(ProfileUtilities.UD_DERIVATION_POINTER)); 3219 } 3220 return null; 3221 } 3222 3223 // Returns the ElementDefinition for the 'root' ancestor of the current element 3224 private ElementDefinition getRootElement(ElementDefinition e) { 3225 if (!e.hasBase()) 3226 return null; 3227 String basePath = e.getBase().getPath(); 3228 String url = "http://hl7.org/fhir/StructureDefinition/" + (basePath.contains(".") ? basePath.substring(0, basePath.indexOf(".")) : basePath); 3229 try { 3230 return getElementById(url, basePath); 3231 } catch (FHIRException except) { 3232 // Likely a logical model, so this is ok 3233 return null; 3234 } 3235 } 3236 private void checkInScope(List<ElementDefinition> stack, List<ElementDefinition> excluded) { 3237 if (stack.size() > 2) { 3238 ElementDefinition parent = stack.get(stack.size()-2); 3239 ElementDefinition focus = stack.get(stack.size()-1); 3240 3241 if (excluded.contains(parent) || "0".equals(parent.getMax())) { 3242 excluded.add(focus); 3243 } 3244 } 3245 } 3246 3247 private void generateAnchors(List<ElementDefinition> stack, Map<String, ElementDefinition> allAnchors) { 3248 List<String> list = new ArrayList<>(); 3249 list.add(stack.get(0).getId()); // initialise 3250 for (int i = 1; i < stack.size(); i++) { 3251 ElementDefinition ed = stack.get(i); 3252 List<String> aliases = new ArrayList<>(); 3253 String name = tail(ed.getPath()); 3254 if (name.endsWith("[x]")) { 3255 aliases.add(name); 3256 Set<String> tl = new HashSet<String>(); // guard against duplicate type names - can happn in some versions 3257 for (TypeRefComponent tr : ed.getType()) { 3258 String tc = tr.getWorkingCode(); 3259 if (!tl.contains(tc)) { 3260 aliases.add(name.replace("[x]", Utilities.capitalize(tc))); 3261 aliases.add(name+":"+name.replace("[x]", Utilities.capitalize(tc))); 3262 tl.add(tc); 3263 } 3264 } 3265 } else if (ed.hasSliceName()) { 3266 aliases.add(name+":"+ed.getSliceName()); 3267 // names.add(name); no good generating this? 3268 } else { 3269 aliases.add(name); 3270 } 3271 List<String> generated = new ArrayList<>(); 3272 for (String l : list) { 3273 for (String a : aliases) { 3274 generated.add(l+"."+a); 3275 } 3276 } 3277 list.clear(); 3278 list.addAll(generated); 3279 } 3280 ElementDefinition ed = stack.get(stack.size()-1); 3281 3282 // now we have all the possible names, but some of them might be inappropriate if we've 3283 // already generated a type slicer. On the other hand, if we've already done that, we're 3284 // going to steal any type specific ones off it. 3285 List<String> removed = new ArrayList<>(); 3286 for (String s : list) { 3287 if (!allAnchors.containsKey(s)) { 3288 allAnchors.put(s, ed); 3289 } else if (s.endsWith("[x]")) { 3290 // that belongs on the earlier element 3291 removed.add(s); 3292 } else { 3293 // we delete it from the other 3294 @SuppressWarnings("unchecked") 3295 List<String> other = (List<String>) allAnchors.get(s).getUserData("dict.generator.anchors"); 3296 other.remove(s); 3297 allAnchors.put(s, ed); 3298 } 3299 } 3300 list.removeAll(removed); 3301 ed.setUserData("dict.generator.anchors", list); 3302 } 3303 3304 private void addToStack(List<ElementDefinition> stack, ElementDefinition ec) { 3305 while (!stack.isEmpty() && !isParent(stack.get(stack.size()-1), ec)) { 3306 stack.remove(stack.size()-1); 3307 } 3308 stack.add(ec); 3309 } 3310 3311 private boolean isParent(ElementDefinition ed, ElementDefinition ec) { 3312 return ec.getPath().startsWith(ed.getPath()+"."); 3313 } 3314 3315 private List<String> makeAnchors(ElementDefinition ed, String anchorPrefix) { 3316 List<String> list = (List<String>) ed.getUserData("dict.generator.anchors"); 3317 List<String> res = new ArrayList<>(); 3318 res.add(anchorPrefix + ed.getId()); 3319 for (String s : list) { 3320 if (!s.equals(ed.getId())) { 3321 res.add(anchorPrefix + s); 3322 } 3323 } 3324 return res; 3325 } 3326 3327 3328 3329 private void link(XhtmlNode x, String id, String anchorPrefix) { 3330 var ah = x.ah("#" + anchorPrefix + id); 3331 ah.attribute("title", "link to here"); 3332 ah.attribute("class", "self-link"); 3333 var svg = ah.svg(); 3334 svg.attribute("viewBox", "0 0 1792 1792"); 3335 svg.attribute("width", "16"); 3336 svg.attribute("height", "16"); 3337 svg.attribute("class", "self-link"); 3338 svg.path("M1520 1216q0-40-28-68l-208-208q-28-28-68-28-42 0-72 32 3 3 19 18.5t21.5 21.5 15 19 13 25.5 3.5 27.5q0 40-28 68t-68 28q-15 0-27.5-3.5t-25.5-13-19-15-21.5-21.5-18.5-19q-33 31-33 73 0 40 28 68l206 207q27 27 68 27 40 0 68-26l147-146q28-28 28-67zm-703-705q0-40-28-68l-206-207q-28-28-68-28-39 0-68 27l-147 146q-28 28-28 67 0 40 28 68l208 208q27 27 68 27 42 0 72-31-3-3-19-18.5t-21.5-21.5-15-19-13-25.5-3.5-27.5q0-40 28-68t68-28q15 0 27.5 3.5t25.5 13 19 15 21.5 21.5 18.5 19q33-31 33-73zm895 705q0 120-85 203l-147 146q-83 83-203 83-121 0-204-85l-206-207q-83-83-83-203 0-123 88-209l-88-88q-86 88-208 88-120 0-204-84l-208-208q-84-84-84-204t85-203l147-146q83-83 203-83 121 0 204 85l206 207q83 83 83 203 0 123-88 209l88 88q86-88 208-88 120 0 204 84l208 208q84 84 84 204z"); 3339 } 3340 3341 private boolean isProfiledExtension(ElementDefinition ec) { 3342 return ec.getType().size() == 1 && "Extension".equals(ec.getType().get(0).getWorkingCode()) && ec.getType().get(0).hasProfile(); 3343 } 3344 3345 private ElementDefinition getExtensionValueDefinition(StructureDefinition extDefn) { 3346 for (ElementDefinition ed : extDefn.getSnapshot().getElement()) { 3347 if (ed.getPath().startsWith("Extension.value")) 3348 return ed; 3349 } 3350 return null; 3351 } 3352 3353 public XhtmlNode compareMarkdown(String location, PrimitiveType md, PrimitiveType compare, int mode) throws FHIRException, IOException { 3354 if (compare == null || mode == GEN_MODE_DIFF) { 3355 if (md.hasValue()) { 3356 String xhtml = hostMd.processMarkdown(location, md); 3357 if (Utilities.noString(xhtml)) { 3358 return null; 3359 } 3360 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 3361 try { 3362 renderStatusDiv(md, x).add(new XhtmlParser().parseFragment(xhtml)); 3363 } catch (Exception e) { 3364 x.span("color: maroon").tx(e.getLocalizedMessage()); 3365 } 3366 return x; 3367 } else { 3368 return null; 3369 } 3370 } else if (areEqual(compare, md)) { 3371 if (md.hasValue()) { 3372 String xhtml = "<div>"+hostMd.processMarkdown(location, md)+"</div>"; 3373 XhtmlNode div = new XhtmlParser().parseFragment(xhtml); 3374 for (XhtmlNode n : div.getChildNodes()) { 3375 if (n.getNodeType() == NodeType.Element) { 3376 n.style(unchangedStyle()); 3377 } 3378 } 3379 return div; 3380 } else { 3381 return null; 3382 } 3383 } else { 3384 XhtmlNode ndiv = new XhtmlNode(NodeType.Element, "div"); 3385 if (md.hasValue()) { 3386 String xhtml = "<div>"+hostMd.processMarkdown(location, md)+"</div>"; 3387 XhtmlNode div = new XhtmlParser().parseFragment(xhtml); 3388 ndiv.copyAllContent(div); 3389 } 3390 if (compare.hasValue()) { 3391 String xhtml = "<div>"+hostMd.processMarkdown(location, compare)+"</div>"; 3392 XhtmlNode div = new XhtmlParser().parseFragment(xhtml); 3393 for (XhtmlNode n : div.getChildNodes()) { 3394 if (n.getNodeType() == NodeType.Element) { 3395 n.style(removedStyle()); 3396 } 3397 } 3398 ndiv.br(); 3399 ndiv.copyAllContent(div); 3400 } 3401 return ndiv; 3402 } 3403 } 3404 3405 private boolean areEqual(PrimitiveType compare, PrimitiveType md) { 3406 if (compare == null && md == null) { 3407 return true; 3408 } else if (compare != null && md != null) { 3409 String one = compare.getValueAsString(); 3410 String two = md.getValueAsString(); 3411 if (one == null && two == null) { 3412 return true; 3413 } else if (one != null && one.equals(two)) { 3414 return true; 3415 } 3416 } 3417 return false; 3418 } 3419 3420 public XhtmlNode compareString(String newStr, Base source, String nLink, String name, Base parent, String oldStr, String oLink, int mode) { 3421 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 3422 if (mode != GEN_MODE_KEY) { 3423 if (newStr != null) { 3424 renderStatus(source, x).ah(nLink).tx(newStr); 3425 } else if (VersionComparisonAnnotation.hasDeleted(parent, name)) { 3426 PrimitiveType p = (PrimitiveType) VersionComparisonAnnotation.getDeletedItem(parent, name); 3427 renderStatus(p, x).tx(p.primitiveValue()); 3428 } else { 3429 return null; 3430 } 3431 } else if (oldStr==null || oldStr.isEmpty()) { 3432 if (newStr==null || newStr.isEmpty()) { 3433 return null; 3434 } else { 3435 renderStatus(source, x).ah(nLink).tx(newStr); 3436 } 3437 } else if (oldStr!=null && !oldStr.isEmpty() && (newStr==null || newStr.isEmpty())) { 3438 if (mode == GEN_MODE_DIFF) { 3439 return null; 3440 } else { 3441 removed(x).ah(oLink).tx(oldStr); 3442 } 3443 } else if (oldStr.equals(newStr)) { 3444 if (mode==GEN_MODE_DIFF) { 3445 return null; 3446 } else { 3447 unchanged(x).ah(nLink).tx(newStr); 3448 } 3449 } else if (newStr.startsWith(oldStr)) { 3450 unchanged(x).ah(oLink).tx(oldStr); 3451 renderStatus(source, x).ah(nLink).tx(newStr.substring(oldStr.length())); 3452 } else { 3453 // TODO: improve comparision in this fall-through case, by looking for matches in sub-paragraphs? 3454 renderStatus(source, x).ah(nLink).tx(newStr); 3455 removed(x).ah(oLink).tx(oldStr); 3456 } 3457 return x; 3458 } 3459 3460 public boolean compareString(XhtmlNode x, String newStr, Base source, String nLink, String name, Base parent, String oldStr, String oLink, int mode) { 3461 XhtmlNode x1 = compareString(newStr, source, nLink, name, parent, oldStr, oLink, mode); 3462 if (x1 == null) { 3463 return false; 3464 } else { 3465 x.getChildNodes().addAll(x1.getChildNodes()); 3466 return true; 3467 } 3468 } 3469 3470 public XhtmlNode unchanged(XhtmlNode x) { 3471 return x.span(unchangedStyle()); 3472 } 3473 3474 private String unchangedStyle() { 3475 return "color:DarkGray"; 3476 } 3477 3478 public XhtmlNode removed(XhtmlNode x) { 3479 return x.span(removedStyle()); 3480 } 3481 3482 private String removedStyle() { 3483 return "color:DarkGray;text-decoration:line-through"; 3484 } 3485 3486 private void generateElementInner(XhtmlNode tbl, StructureDefinition sd, ElementDefinition d, int mode, ElementDefinition value, ElementDefinition compare, ElementDefinition compareValue, boolean strikethrough) throws FHIRException, IOException { 3487 boolean root = !d.getPath().contains("."); 3488 boolean slicedExtension = d.hasSliceName() && (d.getPath().endsWith(".extension") || d.getPath().endsWith(".modifierExtension")); 3489// int slicedExtensionMode = (mode == GEN_MODE_KEY) && slicedExtension ? GEN_MODE_SNAP : mode; // see ProfileUtilities.checkExtensionDoco / Task 3970 3490 if (d.hasSliceName()) { 3491 tableRow(tbl, "Slice Name", "profiling.html#slicing", strikethrough, compareString(d.getSliceName(), d.getSliceNameElement(), null, (compare != null ? compare.getSliceName() : null), d, null, "sliceName", mode)); 3492 tableRow(tbl, "Slice Constraining", "profiling.html#slicing", strikethrough, compareString(encodeValue(d.getSliceIsConstrainingElement()), d.getSliceIsConstrainingElement(), null, (compare != null ? encodeValue(compare.getSliceIsConstrainingElement()) : null), d, null, "sliceName", mode)); 3493 } 3494 3495 tableRow(tbl, "Definition", null, strikethrough, compareMarkdown(sd.getName(), d.getDefinitionElement(), (compare==null) || slicedExtension ? null : compare.getDefinitionElement(), mode)); 3496 tableRow(tbl, "Short", null, strikethrough, compareString(d.hasShort() ? d.getShort() : null, d.getShortElement(), null, "short", d, compare!= null && compare.hasShortElement() ? compare.getShort() : null, null, mode)); 3497 tableRow(tbl, "Comments", null, strikethrough, compareMarkdown(sd.getName(), d.getCommentElement(), (compare==null) || slicedExtension ? null : compare.getCommentElement(), mode)); 3498 tableRow(tbl, "Note", null, strikethrough, businessIdWarning(sd.getName(), tail(d.getPath()))); 3499 tableRow(tbl, "Control", "conformance-rules.html#conformance", strikethrough, describeCardinality(d, compare, mode)); 3500 tableRow(tbl, "Binding", "terminologies.html", strikethrough, describeBinding(sd, d, d.getPath(), compare, mode)); 3501 if (d.hasContentReference()) { 3502 tableRow(tbl, "Type", null, strikethrough, "See " + d.getContentReference().substring(1)); 3503 } else { 3504 tableRow(tbl, "Type", "datatypes.html", strikethrough, describeTypes(d.getType(), false, d, compare, mode, value, compareValue, sd)); 3505 } 3506 if (d.hasExtension(ToolingExtensions.EXT_DEF_TYPE)) { 3507 tableRow(tbl, "Default Type", "datatypes.html", strikethrough, ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_DEF_TYPE)); 3508 } 3509 if (d.hasExtension(ToolingExtensions.EXT_TYPE_SPEC)) { 3510 tableRow(tbl, Utilities.pluralize("Type Specifier", d.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC).size()), "datatypes.html", strikethrough, formatTypeSpecifiers(d)); 3511 } 3512 if (d.getPath().endsWith("[x]") && !d.prohibited()) { 3513 tableRow(tbl, "[x] Note", null, strikethrough).ahWithText("See ", spec("formats.html#choice"), null, "Choice of Data Types", " for further information about how to use [x]"); 3514 } 3515 tableRow(tbl, "Is Modifier", "conformance-rules.html#ismodifier", strikethrough, presentModifier(d, mode, compare)); 3516 if (d.getMustHaveValue()) { 3517 tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", strikethrough, "This primitive type must have a value (the value must be present, and cannot be replaced by an extension)"); 3518 } else if (d.hasValueAlternatives()) { 3519 tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", strikethrough, renderCanonicalList("This primitive type may be present, or absent if replaced by one of the following extensions: ", d.getValueAlternatives())); 3520 } else if (hasPrimitiveTypes(d)) { 3521 tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", strikethrough, "This primitive element may be present, or absent, or replaced by an extension"); 3522 } 3523 if (ToolingExtensions.hasAllowedUnits(d)) { 3524 tableRow(tbl, "Allowed Units", "http://hl7.org/fhir/extensions/StructureDefinition-elementdefinition-allowedUnits.html", strikethrough, describeAllowedUnits(d)); 3525 } 3526 tableRow(tbl, "Must Support", "conformance-rules.html#mustSupport", strikethrough, displayBoolean(d.getMustSupport(), d.getMustSupportElement(), "mustSupport", d, compare==null ? null : compare.getMustSupportElement(), mode)); 3527 if (d.getMustSupport()) { 3528 if (hasMustSupportTypes(d.getType())) { 3529 tableRow(tbl, "Must Support Types", "datatypes.html", strikethrough, describeTypes(d.getType(), true, d, compare, mode, null, null, sd)); 3530 } else if (hasChoices(d.getType())) { 3531 tableRow(tbl, "Must Support Types", "datatypes.html", strikethrough, "No must-support rules about the choice of types/profiles"); 3532 } 3533 } 3534 if (root && sd.getKind() == StructureDefinitionKind.LOGICAL) { 3535 tableRow(tbl, "Logical Model", null, strikethrough, ToolingExtensions.readBoolExtension(sd, ToolingExtensions.EXT_LOGICAL_TARGET) ? "This logical model can be the target of a reference" : "This logical model cannot be the target of a reference"); 3536 } 3537 3538 if (root && sd.hasExtension(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) { 3539 tableRow(tbl, "Impose Profile", "http://hl7.org/fhir/extensions/StructureDefinition-structuredefinition-imposeProfile.html", strikethrough, 3540 renderCanonicalListExt("This profile also requires that the instance also conform this additional profile: ", sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_IMPOSE_PROFILE))); 3541 } 3542 if (root && sd.hasExtension(ToolingExtensions.EXT_SD_COMPLIES_WITH_PROFILE)) { 3543 tableRow(tbl, "Complies with Profile", "http://hl7.org/fhir/extensions/StructureDefinition-structuredefinition-compliesWithProfile.html", strikethrough, 3544 renderCanonicalListExt("This profile compiles with the profile ", sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_COMPLIES_WITH_PROFILE))); 3545 } 3546 tableRow(tbl, "Obligations", null, strikethrough, describeObligations(d, root, sd)); 3547 3548 if (d.hasExtension(ToolingExtensions.EXT_EXTENSION_STYLE)) { 3549 String es = d.getExtensionString(ToolingExtensions.EXT_EXTENSION_STYLE); 3550 if ("named-elements".equals(es)) { 3551 if (context.hasLink(KnownLinkType.JSON_NAMES)) { 3552 tableRow(tbl, "Extension Style", context.getLink(KnownLinkType.JSON_NAMES), strikethrough, "This element can be extended by named JSON elements"); 3553 } else { 3554 tableRow(tbl, "Extension Style", ToolingExtensions.WEB_EXTENSION_STYLE, strikethrough, "This element can be extended by named JSON elements"); 3555 } 3556 } 3557 } 3558 3559 if (!d.getPath().contains(".") && ToolingExtensions.hasExtension(sd, ToolingExtensions.EXT_BINDING_STYLE)) { 3560 tableRow(tbl, "Binding Style", ToolingExtensions.WEB_BINDING_STYLE, strikethrough, 3561 "This type can be bound to a value set using the " + ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_BINDING_STYLE)+" binding style"); 3562 } 3563 3564 if (d.hasExtension(ToolingExtensions.EXT_DATE_FORMAT)) { 3565 tableRow(tbl, "Date Format", null, strikethrough, ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_DATE_FORMAT)); 3566 } 3567 String ide = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_ID_EXPECTATION); 3568 if (ide != null) { 3569 if (ide.equals("optional")) { 3570 tableRow(tbl, "ID Expectation", null, strikethrough, "Id may or not be present (this is the default for elements but not resources)"); 3571 } else if (ide.equals("required")) { 3572 tableRow(tbl, "ID Expectation", null, strikethrough, "Id is required to be present (this is the default for resources but not elements)"); 3573 } else if (ide.equals("required")) { 3574 tableRow(tbl, "ID Expectation", null, strikethrough, "An ID is not allowed in this context"); 3575 } 3576 } 3577 // tooling extensions for formats 3578 if (ToolingExtensions.hasExtensions(d, ToolingExtensions.EXT_JSON_EMPTY, ToolingExtensions.EXT_JSON_PROP_KEY, ToolingExtensions.EXT_JSON_NULLABLE, 3579 ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) { 3580 tableRow(tbl, "JSON Format", null, strikethrough, describeJson(d)); 3581 } 3582 if (d.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE) || sd.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE) || d.hasExtension(ToolingExtensions.EXT_XML_NAME) || (root && sd.hasExtension(ToolingExtensions.EXT_XML_NO_ORDER)) || 3583 d.hasRepresentation()) { 3584 tableRow(tbl, "XML Format", null, strikethrough, describeXml(sd, d, root)); 3585 } 3586 3587 if (d.hasExtension(ToolingExtensions.EXT_IMPLIED_PREFIX)) { 3588 tableRow(tbl, "String Format", null, strikethrough).codeWithText("When this element is read ", ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_IMPLIED_PREFIX), "is prefixed to the value before validation"); 3589 } 3590 3591 if (d.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) { 3592 StandardsStatus ss = StandardsStatus.fromCode(d.getExtensionString(ToolingExtensions.EXT_STANDARDS_STATUS)); 3593 // gc.addStyledText("Standards Status = "+ss.toDisplay(), ss.getAbbrev(), "black", ss.getColor(), baseSpecUrl()+, true); 3594 StructureDefinition sdb = context.getContext().fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 3595 if (sdb != null) { 3596 StandardsStatus base = determineStandardsStatus(sdb, (ElementDefinition) d.getUserData("derived.pointer")); 3597 if (base != null) { 3598 tableRow(tbl, "Standards Status", "versions.html#std-process", strikethrough, ss.toDisplay()+" (from "+base.toDisplay()+")"); 3599 } else { 3600 tableRow(tbl, "Standards Status", "versions.html#std-process", strikethrough, ss.toDisplay()); 3601 } 3602 } else { 3603 tableRow(tbl, "Standards Status", "versions.html#std-process", strikethrough, ss.toDisplay()); 3604 } 3605 } 3606 if (mode != GEN_MODE_DIFF && d.hasIsSummary()) { 3607 tableRow(tbl, "Summary", "search.html#summary", strikethrough, Boolean.toString(d.getIsSummary())); 3608 } 3609 tableRow(tbl, "Requirements", null, strikethrough, compareMarkdown(sd.getName(), d.getRequirementsElement(), (compare==null) || slicedExtension ? null : compare.getRequirementsElement(), mode)); 3610 tableRow(tbl, "Label", null, strikethrough, compareString(d.getLabel(), d.getLabelElement(), null, "label", d, (compare != null ? compare.getLabel() : null), null, mode)); 3611 tableRow(tbl, "Alternate Names", null, strikethrough, compareSimpleTypeLists(d.getAlias(), ((compare==null) || slicedExtension ? null : compare.getAlias()), mode)); 3612 tableRow(tbl, "Definitional Codes", null, strikethrough, compareDataTypeLists(d.getCode(), ((compare==null) || slicedExtension ? null : compare.getCode()), mode)); 3613 tableRow(tbl, "Min Value", null, strikethrough, compareString(d.hasMinValue() ? encodeValue(d.getMinValue()) : null, d.getMinValue(), null, "minValue", d, compare!= null && compare.hasMinValue() ? encodeValue(compare.getMinValue()) : null, null, mode)); 3614 tableRow(tbl, "Max Value", null, strikethrough, compareString(d.hasMaxValue() ? encodeValue(d.getMaxValue()) : null, d.getMaxValue(), null, "maxValue", d, compare!= null && compare.hasMaxValue() ? encodeValue(compare.getMaxValue()) : null, null, mode)); 3615 tableRow(tbl, "Max Length", null, strikethrough, compareString(d.hasMaxLength() ? toStr(d.getMaxLength()) : null, d.getMaxLengthElement(), null, "maxLength", d, compare!= null && compare.hasMaxLengthElement() ? toStr(compare.getMaxLength()) : null, null, mode)); 3616 tableRow(tbl, "Value Required", null, strikethrough, compareString(encodeValue(d.getMustHaveValueElement()), d.getMustHaveValueElement(), null, (compare != null ? encodeValue(compare.getMustHaveValueElement()) : null), d, null, "mustHaveValueElement", mode)); 3617 tableRow(tbl, "Value Alternatives", null, strikethrough, compareSimpleTypeLists(d.getValueAlternatives(), ((compare==null) || slicedExtension ? null : compare.getValueAlternatives()), mode)); 3618 tableRow(tbl, "Default Value", null, strikethrough, encodeValue(d.getDefaultValue(), "defaultValue", d, compare==null ? null : compare.getDefaultValue(), mode)); 3619 tableRow(tbl, "Meaning if Missing", null, strikethrough, d.getMeaningWhenMissing()); 3620 tableRow(tbl, "Fixed Value", null, strikethrough, encodeValue(d.getFixed(), "fixed", d, compare==null ? null : compare.getFixed(), mode)); 3621 tableRow(tbl, "Pattern Value", null, strikethrough, encodeValue(d.getPattern(), "pattern", d, compare==null ? null : compare.getPattern(), mode)); 3622 tableRow(tbl, "Example", null, strikethrough, encodeValues(d.getExample())); 3623 tableRow(tbl, "Invariants", null, strikethrough, invariants(d.getConstraint(), compare==null ? null : compare.getConstraint(), d, mode)); 3624 tableRow(tbl, "LOINC Code", null, strikethrough, getMapping(sd, d, LOINC_MAPPING, compare, mode)); 3625 tableRow(tbl, "SNOMED-CT Code", null, strikethrough, getMapping(sd, d, SNOMED_MAPPING, compare, mode)); 3626 tbl.tx("\r\n"); 3627 } 3628 3629 private XhtmlNode presentModifier(ElementDefinition d, int mode, ElementDefinition compare) throws FHIRException, IOException { 3630 XhtmlNode x1 = compareString(encodeValue(d.getIsModifierElement()), d.getIsModifierElement(), null, "isModifier", d, compare == null ? null : encodeValue(compare.getIsModifierElement()), null, mode); 3631 if (x1 != null) { 3632 XhtmlNode x2 = compareString(encodeValue(d.getIsModifierReasonElement()), d.getIsModifierReasonElement(), null, "isModifierReason", d, compare == null ? null : encodeValue(compare.getIsModifierReasonElement()), null, mode); 3633 if (x2 != null) { 3634 x1.tx(" because "); 3635 x1.copyAllContent(x2); 3636 } 3637 } 3638 return x1; 3639 } 3640 3641 private String spec(String name) { 3642 return Utilities.pathURL(VersionUtilities.getSpecUrl(context.getWorker().getVersion()) , name); 3643 } 3644 3645 private XhtmlNode describeXml(StructureDefinition profile, ElementDefinition d, boolean root) { 3646 XhtmlNode ret = new XhtmlNode(NodeType.Element, "div"); 3647 for (PropertyRepresentation pr : PropertyRepresentation.values()) { 3648 if (d.hasRepresentation(pr)) { 3649 switch (pr) { 3650 case CDATEXT: 3651 ret.tx("This property is represented as CDA Text in the XML."); 3652 break; 3653 case TYPEATTR: 3654 ret.codeWithText("The type of this property is determined using the ", "xsi:type", "attribute."); 3655 break; 3656 case XHTML: 3657 ret.tx("This property is represented as XHTML Text in the XML."); 3658 break; 3659 case XMLATTR: 3660 ret.tx("In the XML format, this property is represented as an attribute."); 3661 break; 3662 case XMLTEXT: 3663 ret.tx("In the XML format, this property is represented as unadorned text."); 3664 break; 3665 default: 3666 } 3667 } 3668 } 3669 String name = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_XML_NAMESPACE); 3670 if (name == null && root) { 3671 name = ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_XML_NAMESPACE); 3672 } 3673 if (name != null) { 3674 ret.codeWithText("In the XML format, this property has the namespace ", name, "."); 3675 } 3676 name = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_XML_NAME); 3677 if (name != null) { 3678 ret.codeWithText("In the XML format, this property has the actual name", name, "."); 3679 } 3680 boolean no = root && ToolingExtensions.readBoolExtension(profile, ToolingExtensions.EXT_XML_NO_ORDER); 3681 if (no) { 3682 ret.tx("The children of this property can appear in any order in the XML."); 3683 } 3684 return ret; 3685 } 3686 3687 private XhtmlNode describeJson(ElementDefinition d) { 3688 XhtmlNode ret = new XhtmlNode(NodeType.Element, "div"); 3689 var ul = ret.ul(); 3690 boolean list = ToolingExtensions.countExtensions(d, ToolingExtensions.EXT_JSON_EMPTY, ToolingExtensions.EXT_JSON_PROP_KEY, ToolingExtensions.EXT_JSON_NULLABLE, ToolingExtensions.EXT_JSON_NAME) > 1; 3691 3692 String code = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_JSON_EMPTY); 3693 if (code != null) { 3694 switch (code) { 3695 case "present": 3696 ul.li().tx("The JSON Array for this property is present even when there are no items in the instance (e.g. as an empty array)"); 3697 break; 3698 case "absent": 3699 ul.li().tx("The JSON Array for this property is not present when there are no items in the instance (e.g. never as an empty array)"); 3700 break; 3701 case "either": 3702 ul.li().tx("The JSON Array for this property may be present even when there are no items in the instance (e.g. may be present as an empty array)"); 3703 break; 3704 } 3705 } 3706 String jn = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_JSON_NAME); 3707 if (jn != null) { 3708 if (d.getPath().contains(".")) { 3709 ul.li().codeWithText("This property appears in JSON with the property name ", jn, null); 3710 } else { 3711 ul.li().codeWithText("This type can appear in JSON with the property name ", jn, " (in elements using named extensions)"); 3712 } 3713 } 3714 code = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_JSON_PROP_KEY); 3715 if (code != null) { 3716 ul.li().codeWithText("This repeating object is represented as a single JSON object with named properties. The name of the property (key) is the value of the ", code, " child"); 3717 } 3718 if (ToolingExtensions.readBoolExtension(d, ToolingExtensions.EXT_JSON_NULLABLE)) { 3719 ul.li().tx("This object can be represented as null in the JSON structure (which counts as 'present' for cardinality purposes)"); 3720 } 3721 if (ToolingExtensions.readBoolExtension(d, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) { 3722 ul.li().tx("The type of this element is inferred from the JSON type in the instance"); 3723 } 3724 3725 switch (ul.getChildNodes().size()) { 3726 case 0: return null; 3727 case 1: return ul.getChildNodes().get(0); 3728 default: return ret; 3729 } 3730 } 3731 3732 private XhtmlNode describeObligations(ElementDefinition d, boolean root, StructureDefinition sdx) throws IOException { 3733 XhtmlNode ret = new XhtmlNode(NodeType.Element, "div"); 3734 ObligationsRenderer obr = new ObligationsRenderer(corePath, sdx, d.getPath(), context, hostMd, this); 3735 obr.seeObligations(d.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)); 3736 obr.seeRootObligations(d.getId(), sdx.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)); 3737 if (obr.hasObligations() || (root && (sdx.hasExtension(ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG) || sdx.hasExtension(ToolingExtensions.EXT_OBLIGATION_INHERITS)))) { 3738 XhtmlNode ul = ret.ul(); 3739 if (root) { 3740 if (sdx.hasExtension(ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG)) { 3741 ul.li().tx("This is an obligation profile that only contains obligations and additional bindings"); 3742 } 3743 for (Extension ext : sdx.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_INHERITS)) { 3744 String iu = ext.getValue().primitiveValue(); 3745 XhtmlNode bb = ul.li(); 3746 bb.tx("This profile picks up obligations and additional bindings from "); 3747 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, iu); 3748 if (sd == null) { 3749 bb.code().tx(iu); 3750 } else if (sd.hasWebPath()) { 3751 bb.ah(sd.getWebPath()).tx(sd.present()); 3752 } else { 3753 bb.ah(iu).tx(sd.present()); 3754 } 3755 } 3756 if (ul.isEmpty()) { 3757 ret.remove(ul); 3758 } 3759 } 3760 if (obr.hasObligations()) { 3761 XhtmlNode tbl = ret.table("grid"); 3762 obr.renderTable(tbl.getChildNodes(), true); 3763 if (tbl.isEmpty()) { 3764 ret.remove(tbl); 3765 } 3766 } 3767 return ret.hasChildren() ? ret : null; 3768 } else { 3769 return null; 3770 } 3771 } 3772 3773 private XhtmlNode describeAllowedUnits(ElementDefinition d) { 3774 XhtmlNode ret = new XhtmlNode(NodeType.Element, "div"); 3775 DataType au = ToolingExtensions.getAllowedUnits(d); 3776 if (au instanceof CanonicalType) { 3777 String url = ((CanonicalType) au).asStringValue(); 3778 ValueSet vs = context.getContext().fetchResource(ValueSet.class, url); 3779 ret.tx("Value set "); 3780 genCT(ret, url, vs); 3781 return ret; 3782 } else if (au instanceof CodeableConcept) { 3783 CodeableConcept cc = (CodeableConcept) au; 3784 if (cc.getCoding().size() != 1) { 3785 ret.tx("One of:"); 3786 } 3787 ret.tx(summarise(cc)); 3788 return ret; 3789 } 3790 return null; 3791 } 3792 3793 private void genCT(XhtmlNode x, String url, CanonicalResource cr) { 3794 if (cr == null) { 3795 x.code().tx(url); 3796 } else if (!cr.hasWebPath()) { 3797 x.ah(url).tx(cr.present()); 3798 } else { 3799 x.ah(cr.getWebPath()).tx(cr.present()); 3800 } 3801 } 3802 3803 private boolean hasPrimitiveTypes(ElementDefinition d) { 3804 for (TypeRefComponent tr : d.getType()) { 3805 if (isPrimitive(tr.getCode())) { 3806 return true; 3807 } 3808 } 3809 return false; 3810 } 3811 3812 3813 private XhtmlNode renderCanonicalListExt(String text, List<Extension> list) { 3814 List<CanonicalType> clist = new ArrayList<>(); 3815 for (Extension ext : list) { 3816 if (ext.hasValueCanonicalType()) { 3817 clist.add(ext.getValueCanonicalType()); 3818 } 3819 } 3820 return renderCanonicalList(text, clist); 3821 } 3822 3823 private XhtmlNode renderCanonicalList(String text, List<CanonicalType> list) { 3824 XhtmlNode ret = new XhtmlNode(NodeType.Element, "div"); 3825 ret.tx(text); 3826 var ul = ret.ul(); 3827 for (CanonicalType ct : list) { 3828 CanonicalResource cr = (CanonicalResource) context.getContext().fetchResource(Resource.class, ct.getValue()); 3829 genCT(ul.li(), ct.getValue(), cr); 3830 } 3831 return ret; 3832 } 3833 3834 private StandardsStatus determineStandardsStatus(StructureDefinition sd, ElementDefinition ed) { 3835 if (ed != null && ed.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) { 3836 return StandardsStatus.fromCode(ed.getExtensionString(ToolingExtensions.EXT_STANDARDS_STATUS)); 3837 } 3838 while (sd != null) { 3839 if (sd.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) { 3840 return ToolingExtensions.getStandardsStatus(sd); 3841 } 3842 sd = context.getContext().fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 3843 } 3844 return null; 3845 } 3846 3847 private boolean hasChoices(List<TypeRefComponent> types) { 3848 for (TypeRefComponent type : types) { 3849 if (type.getProfile().size() > 1 || type.getTargetProfile().size() > 1) { 3850 return true; 3851 } 3852 } 3853 return types.size() > 1; 3854 } 3855 3856 private String sliceOrderString(ElementDefinitionSlicingComponent slicing) { 3857 if (slicing.getOrdered()) 3858 return "ordered"; 3859 else 3860 return "unordered"; 3861 } 3862 3863 private void generateSlicing(XhtmlNode tbl, StructureDefinition profile, ElementDefinition ed, ElementDefinitionSlicingComponent slicing, ElementDefinition compare, int mode, boolean strikethrough) throws IOException { 3864 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 3865 3866 x.codeWithText("This element introduces a set of slices on ", ed.getPath(), ". The slices are "); 3867 String newOrdered = sliceOrderString(slicing); 3868 String oldOrdered = (compare==null || !compare.hasSlicing()) ? null : sliceOrderString(compare.getSlicing()); 3869 compareString(x, newOrdered, slicing.getOrderedElement(), null, null, null, oldOrdered, null, mode); 3870 x.tx(" and "); 3871 compareString(x, slicing.hasRules() ? slicing.getRules().getDisplay() : null, slicing.getRulesElement(), null, "rules", slicing, compare!=null && compare.hasSlicing() && compare.getSlicing().hasRules() ? compare.getSlicing().getRules().getDisplay() : null, null, mode); 3872 3873 if (slicing.hasDiscriminator()) { 3874 x.tx(", and can be differentiated using the following discriminators: "); 3875 StatusList<DiscriminatorWithStatus> list = new StatusList<>(); 3876 for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) { 3877 list.add(new DiscriminatorWithStatus(d)); 3878 } 3879 if (compare != null) { 3880 for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) { 3881 list.merge(new DiscriminatorWithStatus(d)); 3882 } 3883 } 3884 x.tx(", and can be differentiated using the following discriminators: "); 3885 var ul = x.ul(); 3886 for (DiscriminatorWithStatus rc : list) { 3887 rc.render(x.li()); 3888 } 3889 } else { 3890 x.tx(", and defines no discriminators to differentiate the slices"); 3891 } 3892 tableRow(tbl, "Slicing", "profiling.html#slicing", strikethrough, x); 3893 tbl.tx("\r\n"); 3894 } 3895 3896 private XhtmlNode tableRow(XhtmlNode x, String name, String defRef, boolean strikethrough) throws IOException { 3897 var tr = x.tr(); 3898 if (strikethrough) { 3899 tr.style("text-decoration: line-through"); 3900 } 3901 addFirstCell(name, defRef, tr); 3902 return tr.td(); 3903 } 3904 3905 3906 private void tableRow(XhtmlNode x, String name, String defRef, boolean strikethrough, XhtmlNode possibleTd) throws IOException { 3907 if (possibleTd != null && !possibleTd.isEmpty()) { 3908 var tr = x.tr(); 3909 if (strikethrough) { 3910 tr.style("text-decoration: line-through"); 3911 } 3912 addFirstCell(name, defRef, tr); 3913 tr.td().copyAllContent(possibleTd); 3914 } 3915 } 3916 3917 private void tableRow(XhtmlNode x, String name, String defRef, boolean strikethrough, String text) throws IOException { 3918 if (!Utilities.noString(text)) { 3919 var tr = x.tr(); 3920 if (strikethrough) { 3921 tr.style("text-decoration: line-through"); 3922 } 3923 addFirstCell(name, defRef, tr); 3924 tr.td().tx(text); 3925 } 3926 } 3927 3928 private void addFirstCell(String name, String defRef, XhtmlNode tr) { 3929 var td = tr.td(); 3930 if (name.length() <= 16) { 3931 td.style("white-space: nowrap"); 3932 } 3933 if (defRef == null) { 3934 td.tx(name); 3935 } else if (Utilities.isAbsoluteUrl(defRef)) { 3936 td.ah(defRef).tx(name); 3937 } else { 3938 td.ah(corePath+defRef).tx(name); 3939 } 3940 } 3941 3942 private String head(String path) { 3943 if (path.contains(".")) 3944 return path.substring(0, path.indexOf(".")); 3945 else 3946 return path; 3947 } 3948 private String nottail(String path) { 3949 if (path.contains(".")) 3950 return path.substring(0, path.lastIndexOf(".")); 3951 else 3952 return path; 3953 } 3954 3955 private XhtmlNode businessIdWarning(String resource, String name) { 3956 if (name.equals("identifier")) { 3957 XhtmlNode ret = new XhtmlNode(NodeType.Element, "div"); 3958 ret.tx("This is a business identifier, not a resource identifier (see "); 3959 ret.ah(corePath + "resource.html#identifiers").tx("discussion"); 3960 ret.tx(")"); 3961 return ret; 3962 } 3963 if (name.equals("version")) {// && !resource.equals("Device")) 3964 XhtmlNode ret = new XhtmlNode(NodeType.Element, "div"); 3965 ret.tx("This is a business versionId, not a resource version id (see "); 3966 ret.ah(corePath + "resource.html#versions").tx("discussion"); 3967 ret.tx(")"); 3968 return ret; 3969 } 3970 return null; 3971 } 3972 3973 private XhtmlNode describeCardinality(ElementDefinition d, ElementDefinition compare, int mode) throws IOException { 3974 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 3975 if (compare==null || mode==GEN_MODE_DIFF) { 3976 if (!d.hasMax() && !d.hasMin()) 3977 return null; 3978 else if (d.getMax() == null) { 3979 renderStatus(d.getMinElement(), x).tx(toStr(d.getMin())); 3980 x.tx("..?"); 3981 } else { 3982 renderStatus(d.getMinElement(), x).tx(toStr(d.getMin())); 3983 x.tx( ".."); 3984 renderStatus(d.getMaxElement(), x).tx( d.getMax()); 3985 } 3986 } else { 3987 if (!(mode==GEN_MODE_DIFF && (d.getMin()==compare.getMin() || d.getMin()==0))) { 3988 compareString(x, toStr(d.getMin()), d.getMinElement(), null, "min", d, toStr(compare.getMin()), null, mode); 3989 } 3990 x.tx(".."); 3991 if (!(mode==GEN_MODE_DIFF && (d.getMax().equals(compare.getMax()) || "1".equals(d.getMax())))) { 3992 compareString(x, d.getMax(), d.getMaxElement(), null, "max", d, compare.getMax(), null, mode); 3993 } 3994 } 3995 XhtmlNode t = compareSimpleTypeLists(d.getCondition(), compare == null ? null : compare.getCondition(), mode); 3996 if (t != null) { 3997 x.br(); 3998 x.tx("This element is affected by the following invariants: "); 3999 x.copyAllContent(t); 4000 } 4001 return x; 4002 } 4003 4004 private boolean hasMustSupportTypes(List<TypeRefComponent> types) { 4005 for (TypeRefComponent tr : types) { 4006 if (isMustSupport(tr)) { 4007 return true; 4008 } 4009 } 4010 return false; 4011 } 4012 4013 private XhtmlNode describeTypes(List<TypeRefComponent> types, boolean mustSupportOnly, ElementDefinition ed, ElementDefinition compare, int mode, ElementDefinition value, ElementDefinition compareValue, StructureDefinition sd) throws FHIRException, IOException { 4014 if (types.isEmpty()) 4015 return null; 4016 4017 List<TypeRefComponent> compareTypes = compare==null ? new ArrayList<>() : compare.getType(); 4018 XhtmlNode ret = new XhtmlNode(NodeType.Element, "div"); 4019 if ((!mustSupportOnly && types.size() == 1 && compareTypes.size() <=1 && (mode != GEN_MODE_DIFF || !VersionComparisonAnnotation.hasDeleted(ed, "type"))) || (mustSupportOnly && mustSupportCount(types) == 1)) { 4020 if (!mustSupportOnly || isMustSupport(types.get(0))) { 4021 describeType(ret, types.get(0), mustSupportOnly, compareTypes.size()==0 ? null : compareTypes.get(0), mode, sd); 4022 } 4023 } else { 4024 boolean first = true; 4025 if (types.size() > 1) { 4026 ret.tx("Choice of: "); 4027 } 4028 Map<String,TypeRefComponent> map = new HashMap<String, TypeRefComponent>(); 4029 for (TypeRefComponent t : compareTypes) { 4030 map.put(t.getCode(), t); 4031 } 4032 for (TypeRefComponent t : types) { 4033 TypeRefComponent compareType = map.get(t.getCode()); 4034 if (compareType!=null) 4035 map.remove(t.getCode()); 4036 if (!mustSupportOnly || isMustSupport(t)) { 4037 if (first) { 4038 first = false; 4039 } else { 4040 ret.tx(", "); 4041 } 4042 describeType(ret, t, mustSupportOnly, compareType, mode, sd); 4043 } 4044 } 4045 for (TypeRefComponent t : map.values()) { 4046 ret.tx(", "); 4047 describeType(removed(ret), t, mustSupportOnly, null, mode, sd); 4048 } 4049 if (mode == GEN_MODE_DIFF) { 4050 for (Base b : VersionComparisonAnnotation.getDeleted(ed, "type")) { 4051 TypeRefComponent t = (TypeRefComponent) b; 4052 ret.tx(", "); 4053 describeType(ret, t, false, null, mode, sd); 4054 } 4055 } 4056 } 4057 if (value != null) { 4058 XhtmlNode xt = processSecondary(mode, value, compareValue, mode, sd); 4059 if (xt != null) { 4060 ret.copyAllContent(xt); 4061 } 4062 } 4063 return ret; 4064 } 4065 4066 private XhtmlNode processSecondary(int mode, ElementDefinition value, ElementDefinition compareValue, int compMode, StructureDefinition sd) throws FHIRException, IOException { 4067 switch (mode) { 4068 case 1: 4069 return null; 4070 case 2: 4071 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4072 x.tx(" (Complex Extension)"); 4073 return x; 4074 case 3: 4075 x = new XhtmlNode(NodeType.Element, "div"); 4076 x.tx(" (Extension Type: "); 4077 x.copyAllContent(describeTypes(value.getType(), false, value, compareValue, compMode, null, null, sd)); 4078 x.tx(")"); 4079 return x; 4080 default: 4081 return null; 4082 } 4083 } 4084 4085 4086 private int mustSupportCount(List<TypeRefComponent> types) { 4087 int c = 0; 4088 for (TypeRefComponent tr : types) { 4089 if (isMustSupport(tr)) { 4090 c++; 4091 } 4092 } 4093 return c; 4094 } 4095 4096 4097 private void describeType(XhtmlNode x, TypeRefComponent t, boolean mustSupportOnly, TypeRefComponent compare, int mode, StructureDefinition sd) throws FHIRException, IOException { 4098 if (t.getWorkingCode() == null) { 4099 return; 4100 } 4101 if (t.getWorkingCode().startsWith("=")) { 4102 return; 4103 } 4104 4105 boolean ts = false; 4106 if (t.getWorkingCode().startsWith("xs:")) { 4107 ts = compareString(x, t.getWorkingCode(), t, null, "code", t, compare==null ? null : compare.getWorkingCode(), null, mode); 4108 } else { 4109 ts = compareString(x, t.getWorkingCode(), t, getTypeLink(t, sd), "code", t, compare==null ? null : compare.getWorkingCode(), compare==null ? null : getTypeLink(compare, sd), mode); 4110 } 4111 4112 if ((!mustSupportOnly && (t.hasProfile() || (compare!=null && compare.hasProfile()))) || isMustSupport(t.getProfile())) { 4113 StatusList<ResolvedCanonical> profiles = analyseProfiles(t.getProfile(), compare == null ? null : compare.getProfile(), mustSupportOnly, mode); 4114 if (profiles.size() > 0) { 4115 if (!ts) { 4116 getTypeLink(unchanged(x), t, sd); 4117 ts = true; 4118 } 4119 x.tx("("); 4120 boolean first = true; 4121 for (ResolvedCanonical rc : profiles) { 4122 if (first) first = false; else x.tx(", "); 4123 rc.render(x); 4124 } 4125 x.tx(")"); 4126 } 4127 } 4128 4129 if ((!mustSupportOnly && (t.hasTargetProfile() || (compare!=null && compare.hasTargetProfile()))) || isMustSupport(t.getTargetProfile())) { 4130 List<ResolvedCanonical> profiles = analyseProfiles(t.getTargetProfile(), compare == null ? null : compare.getTargetProfile(), mustSupportOnly, mode); 4131 if (profiles.size() > 0) { 4132 if (!ts) { 4133 getTypeLink(unchanged(x), t, sd); 4134 } 4135 x.tx("("); // todo: double use of "(" is problematic 4136 boolean first = true; 4137 for (ResolvedCanonical rc : profiles) { 4138 if (first) first = false; else x.tx(", "); 4139 rc.render(x); 4140 } 4141 x.tx(")"); 4142 } 4143 4144 if (!t.getAggregation().isEmpty() || (compare!=null && !compare.getAggregation().isEmpty())) { 4145 4146 for (Enumeration<AggregationMode> a :t.getAggregation()) { 4147 a.setUserData("render.link", corePath + "codesystem-resource-aggregation-mode.html#content"); 4148 } 4149 if (compare!=null) { 4150 for (Enumeration<AggregationMode> a : compare.getAggregation()) { 4151 a.setUserData("render.link", corePath + "codesystem-resource-aggregation-mode.html#content"); 4152 } 4153 } 4154 var xt = compareSimpleTypeLists(t.getAggregation(), compare == null ? null : compare.getAggregation(), mode); 4155 if (xt != null) { 4156 x.copyAllContent(xt); 4157 } 4158 } 4159 } 4160 } 4161 4162 private StatusList<ResolvedCanonical> analyseProfiles(List<CanonicalType> newProfiles, List<CanonicalType> oldProfiles, boolean mustSupportOnly, int mode) { 4163 StatusList<ResolvedCanonical> profiles = new StatusList<ResolvedCanonical>(); 4164 for (CanonicalType pt : newProfiles) { 4165 ResolvedCanonical rc = fetchProfile(pt, mustSupportOnly); 4166 profiles.add(rc); 4167 } 4168 if (oldProfiles!=null && mode != GEN_MODE_DIFF) { 4169 for (CanonicalType pt : oldProfiles) { 4170 profiles.merge(fetchProfile(pt, mustSupportOnly)); 4171 } 4172 } 4173 return profiles; 4174 } 4175 4176 private ResolvedCanonical fetchProfile(CanonicalType pt, boolean mustSupportOnly) { 4177 if (!pt.hasValue()) { 4178 return null; 4179 } 4180 if (!mustSupportOnly || isMustSupport(pt)) { 4181 StructureDefinition p = context.getContext().fetchResource(StructureDefinition.class, pt.getValue()); 4182 return new ResolvedCanonical(pt.getValue(), p); 4183 } else { 4184 return null; 4185 } 4186 } 4187// 4188// private String getTypeProfile(CanonicalType pt, boolean mustSupportOnly) { 4189// StringBuilder b = new StringBuilder(); 4190// if (!mustSupportOnly || isMustSupport(pt)) { 4191// StructureDefinition p = context.getContext().fetchResource(StructureDefinition.class, pt.getValue()); 4192// if (p == null) 4193// b.append(pt.getValue()); 4194// else { 4195// String pth = p.getWebPath(); 4196// b.append("<a href=\"" + Utilities.escapeXml(pth) + "\" title=\"" + pt.getValue() + "\">"); 4197// b.append(p.getName()); 4198// b.append("</a>"); 4199// } 4200// } 4201// return b.toString(); 4202// } 4203 4204 private void getTypeLink(XhtmlNode x, TypeRefComponent t, StructureDefinition sd) { 4205 String s = context.getPkp().getLinkFor(sd.getWebPath(), t.getWorkingCode()); 4206 if (s != null) { 4207 x.ah(s).tx(t.getWorkingCode()); 4208 } else { 4209 x.code().tx(t.getWorkingCode()); 4210 } 4211 } 4212 4213 4214 private String getTypeLink(TypeRefComponent t, StructureDefinition sd) { 4215 String s = context.getPkp().getLinkFor(sd.getWebPath(), t.getWorkingCode()); 4216 return s; 4217 } 4218 4219 private XhtmlNode displayBoolean(boolean value, BooleanType source, String name, Base parent, BooleanType compare, int mode) { 4220 String newValue = value ? "true" : source.hasValue() ? "false" : null; 4221 String oldValue = compare==null || compare.getValue()==null ? null : (compare.getValue()!=true ? null : "true"); 4222 return compareString(newValue, source, null, name, parent, oldValue, null, mode); 4223 } 4224 4225 4226 private XhtmlNode invariants(List<ElementDefinitionConstraintComponent> originalList, List<ElementDefinitionConstraintComponent> compareList, ElementDefinition parent, int mode) throws IOException { 4227 StatusList<InvariantWithStatus> list = new StatusList<>(); 4228 for (ElementDefinitionConstraintComponent v : originalList) { 4229 if (!v.isEmpty()) { 4230 list.add(new InvariantWithStatus(v)); 4231 } 4232 } 4233 if (compareList != null && mode != GEN_MODE_DIFF) { 4234 for (ElementDefinitionConstraintComponent v : compareList) { 4235 list.merge(new InvariantWithStatus(v)); 4236 } 4237 } 4238 if (list.size() == 0) { 4239 return null; 4240 } 4241 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4242 boolean first = true; 4243 for (InvariantWithStatus t : list) { 4244 if (first) first = false; else x.br(); 4245 t.render(x); 4246 } 4247 for (Base b : VersionComparisonAnnotation.getDeleted(parent, "invariant")) { 4248 if (first) first = false; else x.br(); 4249 InvariantWithStatus ts = new InvariantWithStatus((ElementDefinitionConstraintComponent) b); 4250 ts.render(x); 4251 } 4252 return x; 4253 } 4254 4255 private XhtmlNode describeBinding(StructureDefinition sd, ElementDefinition d, String path, ElementDefinition compare, int mode) throws FHIRException, IOException { 4256 if (!d.hasBinding()) 4257 return null; 4258 else { 4259 ElementDefinitionBindingComponent binding = d.getBinding(); 4260 ElementDefinitionBindingComponent compBinding = compare == null ? null : compare.getBinding(); 4261 XhtmlNode bindingDesc = null; 4262 if (binding.hasDescription()) { 4263 MarkdownType newBinding = PublicationHacker.fixBindingDescriptions(context.getContext(), binding.getDescriptionElement()); 4264 if (mode == GEN_MODE_SNAP || mode == GEN_MODE_MS) { 4265 bindingDesc = new XhtmlNode(NodeType.Element, "div"); 4266 bindingDesc.add(new XhtmlParser().parseFragment(hostMd.processMarkdown("Binding.description", newBinding))); 4267 } else { 4268 4269 StringType oldBinding = compBinding != null && compBinding.hasDescription() ? PublicationHacker.fixBindingDescriptions(context.getContext(), compBinding.getDescriptionElement()) : null; 4270 bindingDesc = compareMarkdown("Binding.description", newBinding, oldBinding, mode); 4271 } 4272 } 4273 if (!binding.hasValueSet()) { 4274 return bindingDesc; 4275 } 4276 4277 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4278 var nsp = x.span(); 4279 renderBinding(nsp, binding, compBinding, path, sd, mode); 4280 if (bindingDesc != null) { 4281 if (isSimpleContent(bindingDesc)) { 4282 x.tx(": "); 4283 x.copyAllContent(bindingDesc.getChildNodes().get(0)); 4284 } else { 4285 x.br(); 4286 x.copyAllContent(bindingDesc); 4287 } 4288 } 4289 4290 AdditionalBindingsRenderer abr = new AdditionalBindingsRenderer(context.getPkp(), corePath, sd, d.getPath(), context, hostMd, this); 4291 4292 if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) { 4293 abr.seeMaxBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), compBinding==null ? null : ToolingExtensions.getExtension(compBinding, ToolingExtensions.EXT_MAX_VALUESET), mode!=GEN_MODE_SNAP && mode!=GEN_MODE_MS); 4294 } 4295 if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) { 4296 abr.seeMinBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), compBinding==null ? null : ToolingExtensions.getExtension(compBinding, ToolingExtensions.EXT_MIN_VALUESET), mode!=GEN_MODE_SNAP && mode!=GEN_MODE_MS); 4297 } 4298 if (binding.hasExtension(ToolingExtensions.EXT_BINDING_ADDITIONAL)) { 4299 abr.seeAdditionalBindings(binding.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL), compBinding==null ? null : compBinding.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL), mode!=GEN_MODE_SNAP && mode!=GEN_MODE_MS); 4300 } 4301 4302 if (abr.hasBindings()) { 4303 var tbl = x.table("grid"); 4304 abr.render(tbl.getChildNodes(), true); 4305 } 4306 return x; 4307 } 4308 } 4309 4310 4311 private boolean isSimpleContent(XhtmlNode bindingDesc) { 4312 return bindingDesc.getChildNodes().size() == 1 && bindingDesc.getChildNodes().get(0).isPara(); 4313 } 4314 4315 private void renderBinding(XhtmlNode span, ElementDefinitionBindingComponent binding, ElementDefinitionBindingComponent compare, String path, StructureDefinition sd, int mode) { 4316 compareString(span, conf(binding), binding.getStrengthElement(), null, "strength", binding, compare == null ? null : conf(compare), null, mode); 4317 span.tx(" "); 4318 BindingResolution br = context.getPkp().resolveBinding(sd, binding, path); 4319 compareString(span, br.display, binding.getValueSetElement(), br.url, "valueSet", binding, compare == null ? null : compare.getValueSet(), null, mode); 4320 } 4321 4322 private String stripPara(String s) { 4323 if (s.startsWith("<p>")) { 4324 s = s.substring(3); 4325 } 4326 if (s.trim().endsWith("</p>")) { 4327 s = s.substring(0, s.lastIndexOf("</p>")-1) + s.substring(s.lastIndexOf("</p>") +4); 4328 } 4329 return s; 4330 } 4331 4332 private String conf(ElementDefinitionBindingComponent def) { 4333 if (def.getStrength() == null) { 4334 return "For codes, see "; 4335 } 4336 switch (def.getStrength()) { 4337 case EXAMPLE: 4338 return "For example codes, see "; 4339 case PREFERRED: 4340 return "The codes SHOULD be taken from "; 4341 case EXTENSIBLE: 4342 return "Unless not suitable, these codes SHALL be taken from "; 4343 case REQUIRED: 4344 return "The codes SHALL be taken from "; 4345 default: 4346 return "?sd-conf?"; 4347 } 4348 } 4349 4350 private String encodeValues(List<ElementDefinitionExampleComponent> examples) throws FHIRException, IOException { 4351 StringBuilder b = new StringBuilder(); 4352 boolean first = false; 4353 for (ElementDefinitionExampleComponent ex : examples) { 4354 if (first) 4355 first = false; 4356 else 4357 b.append("<br/>"); 4358 b.append("<b>" + Utilities.escapeXml(ex.getLabel()) + "</b>:" + encodeValue(ex.getValue()) + "\r\n"); 4359 } 4360 return b.toString(); 4361 4362 } 4363 4364 private XhtmlNode encodeValue(DataType value, String name, Base parent, DataType compare, int mode) throws FHIRException, IOException { 4365 String oldValue = encodeValue(compare); 4366 String newValue = encodeValue(value); 4367 return compareString(newValue, value, null, name, parent, oldValue, null, mode); 4368 } 4369 4370 private String encodeValue(DataType value) throws FHIRException, IOException { 4371 if (value == null || value.isEmpty()) 4372 return null; 4373 if (value instanceof PrimitiveType) 4374 return Utilities.escapeXml(((PrimitiveType) value).asStringValue()); 4375 4376 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 4377 XmlParser parser = new XmlParser(); 4378 parser.setOutputStyle(OutputStyle.PRETTY); 4379 parser.compose(bs, null, value); 4380 String[] lines = bs.toString().split("\\r?\\n"); 4381 StringBuilder b = new StringBuilder(); 4382 for (String s : lines) { 4383 if (!Utilities.noString(s) && !s.startsWith("<?")) { // eliminate the xml header 4384 b.append(Utilities.escapeXml(s).replace(" ", " ") + "<br/>"); 4385 } 4386 } 4387 return b.toString(); 4388 4389 } 4390 4391 private XhtmlNode getMapping(StructureDefinition profile, ElementDefinition d, String uri, ElementDefinition compare, int mode) { 4392 String id = null; 4393 for (StructureDefinitionMappingComponent m : profile.getMapping()) { 4394 if (m.hasUri() && m.getUri().equals(uri)) 4395 id = m.getIdentity(); 4396 } 4397 if (id == null) 4398 return null; 4399 String newMap = null; 4400 for (ElementDefinitionMappingComponent m : d.getMapping()) { 4401 if (m.getIdentity().equals(id)) { 4402 newMap = m.getMap(); 4403 break; 4404 } 4405 } 4406 if (compare==null) 4407 return new XhtmlNode(NodeType.Element, "div").tx(newMap); 4408 String oldMap = null; 4409 for (ElementDefinitionMappingComponent m : compare.getMapping()) { 4410 if (m.getIdentity().equals(id)) { 4411 oldMap = m.getMap(); 4412 break; 4413 } 4414 } 4415 4416 return compareString(Utilities.escapeXml(newMap), null, null, "mapping", d, Utilities.escapeXml(oldMap), null, mode); 4417 } 4418 4419 private XhtmlNode compareSimpleTypeLists(List<? extends PrimitiveType> original, List<? extends PrimitiveType> compare, int mode) throws IOException { 4420 return compareSimpleTypeLists(original, compare, mode, ", "); 4421 } 4422 4423 4424 private XhtmlNode compareSimpleTypeLists(List<? extends PrimitiveType> originalList, List<? extends PrimitiveType> compareList, int mode, String separator) throws IOException { 4425 StatusList<ValueWithStatus> list = new StatusList<>(); 4426 for (PrimitiveType v : originalList) { 4427 if (!v.isEmpty()) { 4428 list.add(new ValueWithStatus(v)); 4429 } 4430 } 4431 if (compareList != null && mode != GEN_MODE_DIFF) { 4432 for (PrimitiveType v : compareList) { 4433 list.merge(new ValueWithStatus(v)); 4434 } 4435 } 4436 if (list.size() == 0) { 4437 return null; 4438 } 4439 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4440 boolean first = true; 4441 for (ValueWithStatus t : list) { 4442 if (first) first = false; else x.tx(separator); 4443 t.render(x); 4444 } 4445 return x; 4446 } 4447 4448 4449 private XhtmlNode compareDataTypeLists(List<? extends DataType> original, List<? extends DataType> compare, int mode) throws IOException { 4450 return compareDataTypeLists(original, compare, mode, ", "); 4451 } 4452 4453 4454 private XhtmlNode compareDataTypeLists(List<? extends DataType> originalList, List<? extends DataType> compareList, int mode, String separator) throws IOException { 4455 StatusList<DataValueWithStatus> list = new StatusList<>(); 4456 for (DataType v : originalList) { 4457 if (!v.isEmpty()) { 4458 list.add(new DataValueWithStatus(v)); 4459 } 4460 } 4461 if (compareList != null && mode != GEN_MODE_DIFF) { 4462 for (DataType v : compareList) { 4463 list.merge(new DataValueWithStatus(v)); 4464 } 4465 } 4466 if (list.size() == 0) { 4467 return null; 4468 } 4469 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 4470 boolean first = true; 4471 for (DataValueWithStatus t : list) { 4472 if (first) first = false; else x.tx(separator); 4473 t.render(x); 4474 } 4475 return x; 4476 } 4477 4478 4479 4480 private String summarise(CodeableConcept cc) throws FHIRException { 4481 if (cc.getCoding().size() == 1 && cc.getText() == null) { 4482 return summarise(cc.getCoding().get(0)); 4483 } else if (cc.hasText()) { 4484 return "\"" + cc.getText() + "\""; 4485 } else if (cc.getCoding().size() > 0) { 4486 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 4487 for (Coding c : cc.getCoding()) { 4488 b.append(summarise(c)); 4489 } 4490 return b.toString(); 4491 } else { 4492 throw new FHIRException("Error describing concept - not done yet (no codings, no text)"); 4493 } 4494 } 4495 4496 private String summarise(Coding coding) throws FHIRException { 4497 if ("http://snomed.info/sct".equals(coding.getSystem())) 4498 return "" + translate("sd.summary", "SNOMED CT code") + " " + coding.getCode() + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")"); 4499 if ("http://loinc.org".equals(coding.getSystem())) 4500 return "" + translate("sd.summary", "LOINC code") + " " + coding.getCode() + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")"); 4501 if ("http://unitsofmeasure.org/".equals(coding.getSystem())) 4502 return " (" + translate("sd.summary", "UCUM") + ": " + coding.getCode() + ")"; 4503 CodeSystem cs = context.getContext().fetchCodeSystem(coding.getSystem()); 4504 if (cs == null) 4505 return "<span title=\"" + coding.getSystem() + "\">" + coding.getCode() + "</a>" + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")"); 4506 else 4507 return "<a title=\"" + cs.present() + "\" href=\"" + Utilities.escapeXml(cs.getWebPath()) + "#" + cs.getId() + "-" + coding.getCode() + "\">" + coding.getCode() + "</a>" + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")"); 4508 } 4509 4510}