
001package org.hl7.fhir.r4.conformance; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032import java.io.IOException; 033import java.io.OutputStream; 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.Comparator; 037import java.util.HashMap; 038import java.util.HashSet; 039import java.util.Iterator; 040import java.util.List; 041import java.util.Map; 042import java.util.Set; 043 044import lombok.Getter; 045import lombok.Setter; 046import lombok.extern.slf4j.Slf4j; 047import org.apache.commons.lang3.StringUtils; 048import org.hl7.fhir.exceptions.DefinitionException; 049import org.hl7.fhir.exceptions.FHIRException; 050import org.hl7.fhir.exceptions.FHIRFormatError; 051import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution; 052import org.hl7.fhir.r4.context.IWorkerContext; 053import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult; 054import org.hl7.fhir.r4.elementmodel.ObjectConverter; 055import org.hl7.fhir.r4.elementmodel.Property; 056import org.hl7.fhir.r4.formats.IParser; 057import org.hl7.fhir.r4.model.Base; 058import org.hl7.fhir.r4.model.BooleanType; 059import org.hl7.fhir.r4.model.CanonicalType; 060import org.hl7.fhir.r4.model.CodeType; 061import org.hl7.fhir.r4.model.CodeableConcept; 062import org.hl7.fhir.r4.model.Coding; 063import org.hl7.fhir.r4.model.Element; 064import org.hl7.fhir.r4.model.ElementDefinition; 065import org.hl7.fhir.r4.model.ElementDefinition.AggregationMode; 066import org.hl7.fhir.r4.model.ElementDefinition.DiscriminatorType; 067import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBaseComponent; 068import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 069import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent; 070import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionExampleComponent; 071import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent; 072import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent; 073import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 074import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation; 075import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules; 076import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 077import org.hl7.fhir.r4.model.Enumeration; 078import org.hl7.fhir.r4.model.Enumerations.BindingStrength; 079import org.hl7.fhir.r4.model.Extension; 080import org.hl7.fhir.r4.model.IntegerType; 081import org.hl7.fhir.r4.model.PrimitiveType; 082import org.hl7.fhir.r4.model.Quantity; 083import org.hl7.fhir.r4.model.Resource; 084import org.hl7.fhir.r4.model.StringType; 085import org.hl7.fhir.r4.model.StructureDefinition; 086import org.hl7.fhir.r4.model.StructureDefinition.ExtensionContextType; 087import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionContextComponent; 088import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionDifferentialComponent; 089import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 090import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent; 091import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent; 092import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 093import org.hl7.fhir.r4.model.Type; 094import org.hl7.fhir.r4.model.UriType; 095import org.hl7.fhir.r4.model.ValueSet; 096import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; 097import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 098import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 099import org.hl7.fhir.r4.utils.NarrativeGenerator; 100import org.hl7.fhir.r4.utils.ToolingExtensions; 101import org.hl7.fhir.r4.utils.TranslatingUtilities; 102import org.hl7.fhir.r4.utils.formats.CSVWriter; 103import org.hl7.fhir.r4.utils.formats.XLSXWriter; 104import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 105import org.hl7.fhir.utilities.FhirPublication; 106import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 107import org.hl7.fhir.utilities.TerminologyServiceOptions; 108import org.hl7.fhir.utilities.Utilities; 109import org.hl7.fhir.utilities.VersionUtilities; 110import org.hl7.fhir.utilities.i18n.RenderingI18nContext; 111import org.hl7.fhir.utilities.validation.ValidationMessage; 112import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 113import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 114import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 115import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 116import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 117import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMode; 118import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 119import org.hl7.fhir.utilities.xhtml.XhtmlNode; 120import org.hl7.fhir.utilities.xml.SchematronWriter; 121import org.hl7.fhir.utilities.xml.SchematronWriter.Rule; 122import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType; 123import org.hl7.fhir.utilities.xml.SchematronWriter.Section; 124 125/** 126 * This class provides a set of utility operations for working with Profiles. 127 * Key functionality: * getChildMap --? * getChildList * generateSnapshot: Given 128 * a base (snapshot) profile structure, and a differential profile, generate a 129 * new snapshot profile * closeDifferential: fill out a differential by 130 * excluding anything not mentioned * generateExtensionsTable: generate the HTML 131 * for a hierarchical table presentation of the extensions * generateTable: 132 * generate the HTML for a hierarchical table presentation of a structure * 133 * generateSpanningTable: generate the HTML for a table presentation of a 134 * network of structures, starting at a nominated point * summarize: describe 135 * the contents of a profile 136 * 137 * note to maintainers: Do not make modifications to the snapshot generation 138 * without first changing the snapshot generation test cases to demonstrate the 139 * grounds for your change 140 * 141 * @author Grahame 142 * 143 */ 144@MarkedToMoveToAdjunctPackage 145@Slf4j 146public class ProfileUtilities extends TranslatingUtilities { 147 148 public class ElementRedirection { 149 150 private String path; 151 private ElementDefinition element; 152 153 public ElementRedirection(ElementDefinition element, String path) { 154 this.path = path; 155 this.element = element; 156 } 157 158 public ElementDefinition getElement() { 159 return element; 160 } 161 162 @Override 163 public String toString() { 164 return element.toString() + " : " + path; 165 } 166 167 public String getPath() { 168 return path; 169 } 170 171 } 172 173 public class TypeSlice { 174 private ElementDefinition defn; 175 private String type; 176 177 public TypeSlice(ElementDefinition defn, String type) { 178 super(); 179 this.defn = defn; 180 this.type = type; 181 } 182 183 public ElementDefinition getDefn() { 184 return defn; 185 } 186 187 public String getType() { 188 return type; 189 } 190 191 } 192 193 private static final int MAX_RECURSION_LIMIT = 10; 194 195 public class ExtensionContext { 196 197 private ElementDefinition element; 198 private StructureDefinition defn; 199 200 public ExtensionContext(StructureDefinition ext, ElementDefinition ed) { 201 this.defn = ext; 202 this.element = ed; 203 } 204 205 public ElementDefinition getElement() { 206 return element; 207 } 208 209 public StructureDefinition getDefn() { 210 return defn; 211 } 212 213 public String getUrl() { 214 if (element == defn.getSnapshot().getElement().get(0)) 215 return defn.getUrl(); 216 else 217 return element.getSliceName(); 218 } 219 220 public ElementDefinition getExtensionValueDefinition() { 221 int i = defn.getSnapshot().getElement().indexOf(element) + 1; 222 while (i < defn.getSnapshot().getElement().size()) { 223 ElementDefinition ed = defn.getSnapshot().getElement().get(i); 224 if (ed.getPath().equals(element.getPath())) 225 return null; 226 if (ed.getPath().startsWith(element.getPath() + ".value")) 227 return ed; 228 i++; 229 } 230 return null; 231 } 232 } 233 234 private static final String ROW_COLOR_ERROR = "#ffcccc"; 235 private static final String ROW_COLOR_FATAL = "#ff9999"; 236 private static final String ROW_COLOR_WARNING = "#ffebcc"; 237 private static final String ROW_COLOR_HINT = "#ebf5ff"; 238 public static final int STATUS_OK = 0; 239 public static final int STATUS_HINT = 1; 240 public static final int STATUS_WARNING = 2; 241 public static final int STATUS_ERROR = 3; 242 public static final int STATUS_FATAL = 4; 243 244 private static final String DERIVATION_EQUALS = "derivation.equals"; 245 public static final String DERIVATION_POINTER = "derived.pointer"; 246 public static final String IS_DERIVED = "derived.fact"; 247 public static final String UD_ERROR_STATUS = "error-status"; 248 private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed"; 249 private final boolean ADD_REFERENCE_TO_TABLE = true; 250 251 private boolean useTableForFixedValues = true; 252 @Setter 253 @Getter 254 @Deprecated 255 private boolean debug; 256 257 // note that ProfileUtilities are used re-entrantly internally, so nothing with 258 // process state can be here 259 private final IWorkerContext context; 260 private List<ValidationMessage> messages; 261 private List<String> snapshotStack = new ArrayList<String>(); 262 private ProfileKnowledgeProvider pkp; 263 private boolean igmode; 264 private boolean exception; 265 private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions(FhirPublication.R4); 266 private boolean newSlicingProcessing; 267 268 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) { 269 super(); 270 this.context = context; 271 this.messages = messages; 272 this.pkp = pkp; 273 } 274 275 private class UnusedTracker { 276 private boolean used; 277 } 278 279 public boolean isIgmode() { 280 return igmode; 281 } 282 283 public void setIgmode(boolean igmode) { 284 this.igmode = igmode; 285 } 286 287 public interface ProfileKnowledgeProvider { 288 public class BindingResolution { 289 public String display; 290 public String url; 291 } 292 293 public boolean isPrimitiveType(String typeSimple); 294 295 public boolean isDatatype(String typeSimple); 296 297 public boolean isResource(String typeSimple); 298 299 public boolean hasLinkFor(String typeSimple); 300 301 public String getLinkFor(String corePath, String typeSimple); 302 303 public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, 304 String path) throws FHIRException; 305 306 public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException; 307 308 public String getLinkForProfile(StructureDefinition profile, String url); 309 310 public boolean prependLinks(); 311 312 public String getLinkForUrl(String corePath, String s); 313 } 314 315 public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) 316 throws DefinitionException { 317 if (element.getContentReference() != null) { 318 for (ElementDefinition e : profile.getSnapshot().getElement()) { 319 if (element.getContentReference().equals("#" + e.getId())) 320 return getChildMap(profile, e); 321 } 322 throw new DefinitionException( 323 "Unable to resolve name reference " + element.getContentReference() + " at path " + element.getPath()); 324 325 } else { 326 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 327 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 328 String path = element.getPath(); 329 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 330 ElementDefinition e = elements.get(index); 331 if (e.getPath().startsWith(path + ".")) { 332 // We only want direct children, not all descendants 333 if (!e.getPath().substring(path.length() + 1).contains(".")) 334 res.add(e); 335 } else 336 break; 337 } 338 return res; 339 } 340 } 341 342 public static List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) 343 throws DefinitionException { 344 if (!element.hasSlicing()) 345 throw new Error("getSliceList should only be called when the element has slicing"); 346 347 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 348 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 349 String path = element.getPath(); 350 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 351 ElementDefinition e = elements.get(index); 352 if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) { 353 // We want elements with the same path (until we hit an element that doesn't 354 // start with the same path) 355 if (e.getPath().equals(element.getPath())) 356 res.add(e); 357 } else 358 break; 359 } 360 return res; 361 } 362 363 /** 364 * Given a Structure, navigate to the element given by the path and return the 365 * direct children of that element 366 * 367 * @param structure The structure to navigate into 368 * @param path The path of the element within the structure to get the 369 * children for 370 * @return A List containing the element children (all of them are Elements) 371 */ 372 public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) { 373 return getChildList(profile, path, id, false); 374 } 375 376 public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, 377 boolean diff) { 378 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 379 380 boolean capturing = id == null; 381 if (id == null && !path.contains(".")) 382 capturing = true; 383 384 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 385 for (ElementDefinition e : list) { 386 if (e == null) 387 throw new Error("element = null: " + profile.getUrl()); 388 if (e.getId() == null) 389 throw new Error("element id = null: " + e.toString() + " on " + profile.getUrl()); 390 391 if (!capturing && id != null && e.getId().equals(id)) { 392 capturing = true; 393 } 394 395 // If our element is a slice, stop capturing children as soon as we see the next 396 // slice 397 if (capturing && e.hasId() && id != null && !e.getId().equals(id) && e.getPath().equals(path)) 398 break; 399 400 if (capturing) { 401 String p = e.getPath(); 402 403 if (!Utilities.noString(e.getContentReference()) && path.startsWith(p)) { 404 if (path.length() > p.length()) 405 return getChildList(profile, e.getContentReference() + "." + path.substring(p.length() + 1), null, diff); 406 else 407 return getChildList(profile, e.getContentReference(), null, diff); 408 409 } else if (p.startsWith(path + ".") && !p.equals(path)) { 410 String tail = p.substring(path.length() + 1); 411 if (!tail.contains(".")) { 412 res.add(e); 413 } 414 } 415 } 416 } 417 418 return res; 419 } 420 421 public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, 422 boolean diff) { 423 return getChildList(structure, element.getPath(), element.getId(), diff); 424 } 425 426 public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) { 427 return getChildList(structure, element.getPath(), element.getId(), false); 428 } 429 430 public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException { 431 if (base == null) 432 throw new DefinitionException("no base profile provided"); 433 if (derived == null) 434 throw new DefinitionException("no derived structure provided"); 435 436 for (StructureDefinitionMappingComponent baseMap : base.getMapping()) { 437 boolean found = false; 438 for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) { 439 if (derivedMap.getUri() != null && derivedMap.getUri().equals(baseMap.getUri())) { 440 found = true; 441 break; 442 } 443 } 444 if (!found) 445 derived.getMapping().add(baseMap); 446 } 447 } 448 449 /** 450 * Given a base (snapshot) profile structure, and a differential profile, 451 * generate a new snapshot profile 452 * 453 * @param base - the base structure on which the differential will 454 * be applied 455 * @param differential - the differential to apply to the base 456 * @param url - where the base has relative urls for profile 457 * references, these need to be converted to absolutes 458 * by prepending this URL (e.g. the canonical URL) 459 * @param webUrl - where the base has relative urls in markdown, these 460 * need to be converted to absolutes by prepending this 461 * URL (this is not the same as the canonical URL) 462 * @param trimDifferential - if this is true, then the snap short generator will 463 * remove any material in the element definitions that 464 * is not different to the base 465 * @return 466 * @throws FHIRException 467 * @throws DefinitionException 468 * @throws Exception 469 */ 470 public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, 471 String profileName) throws DefinitionException, FHIRException { 472 if (base == null) 473 throw new DefinitionException("no base profile provided"); 474 if (derived == null) 475 throw new DefinitionException("no derived structure provided"); 476 477 if (snapshotStack.contains(derived.getUrl())) 478 throw new DefinitionException( 479 "Circular snapshot references detected; cannot generate snapshot (stack = " + snapshotStack.toString() + ")"); 480 snapshotStack.add(derived.getUrl()); 481 482 if (!Utilities.noString(webUrl) && !webUrl.endsWith("/")) 483 webUrl = webUrl + '/'; 484 485 derived.setSnapshot(new StructureDefinitionSnapshotComponent()); 486 487 try { 488 // so we have two lists - the base list, and the differential list 489 // the differential list is only allowed to include things that are in the base 490 // list, but 491 // is allowed to include them multiple times - thereby slicing them 492 493 // our approach is to walk through the base list, and see whether the 494 // differential 495 // says anything about them. 496 int baseCursor = 0; 497 int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by 498 // longer paths 499 500 if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") 501 && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) 502 throw new Error("type on first differential element!"); 503 504 for (ElementDefinition e : derived.getDifferential().getElement()) 505 e.clearUserData(GENERATED_IN_SNAPSHOT); 506 507 // we actually delegate the work to a subroutine so we can re-enter it with a 508 // different cursors 509 StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential()); // we make a copy here 510 // because we're sometimes 511 // going to hack the 512 // differential while 513 // processing it. Have to 514 // migrate user data back 515 // afterwards 516 517 processPaths("", derived.getSnapshot(), base.getSnapshot(), diff, baseCursor, diffCursor, 518 base.getSnapshot().getElement().size() - 1, 519 derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size() - 1 : -1, url, webUrl, 520 derived.present(), null, null, false, base.getUrl(), null, false, new ArrayList<ElementRedirection>(), base); 521 if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty()) 522 throw new Error("type on first snapshot element for " + derived.getSnapshot().getElementFirstRep().getPath() 523 + " in " + derived.getUrl() + " from " + base.getUrl()); 524 updateMaps(base, derived); 525 526 527 log.debug("Differential: "); 528 for (ElementDefinition ed : derived.getDifferential().getElement()) 529 log.debug(" " + ed.getPath() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." 530 + ed.getMax() + "]" + sliceSummary(ed) + " id = " + ed.getId() + " " + constraintSummary(ed)); 531 log.debug("Snapshot: "); 532 for (ElementDefinition ed : derived.getSnapshot().getElement()) 533 log.debug(" " + ed.getPath() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." 534 + ed.getMax() + "]" + sliceSummary(ed) + " id = " + ed.getId() + " " + constraintSummary(ed)); 535 536 setIds(derived, false); 537 // Check that all differential elements have a corresponding snapshot element 538 for (ElementDefinition e : diff.getElement()) { 539 if (!e.hasUserData("diff-source")) 540 throw new Error("Unxpected internal condition - no source on diff element"); 541 else { 542 if (e.hasUserData(DERIVATION_EQUALS)) 543 ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_EQUALS, e.getUserData(DERIVATION_EQUALS)); 544 if (e.hasUserData(DERIVATION_POINTER)) 545 ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_POINTER, e.getUserData(DERIVATION_POINTER)); 546 } 547 if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) { 548 log.error("Error in snapshot generation: Differential for " + derived.getUrl() + " with " 549 + (e.hasId() ? "id: " + e.getId() : "path: " + e.getPath()) 550 + " has an element that is not marked with a snapshot match"); 551 if (exception) 552 throw new DefinitionException("Snapshot for " + derived.getUrl() 553 + " does not contain an element that matches an existing differential element that has " 554 + (e.hasId() ? "id: " + e.getId() : "path: " + e.getPath())); 555 else 556 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, 557 "Snapshot for " + derived.getUrl() 558 + " does not contain an element that matches an existing differential element that has id: " 559 + e.getId(), 560 ValidationMessage.IssueSeverity.ERROR)); 561 } 562 } 563 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 564 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 565 if (!ed.hasBase()) { 566 ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax()); 567 } 568 } 569 } 570 } catch (Exception e) { 571 // if we had an exception generating the snapshot, make sure we don't leave any 572 // half generated snapshot behind 573 derived.setSnapshot(null); 574 throw e; 575 } 576 } 577 578 private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) { 579 StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent(); 580 for (ElementDefinition sed : source.getElement()) { 581 ElementDefinition ted = sed.copy(); 582 diff.getElement().add(ted); 583 ted.setUserData("diff-source", sed); 584 } 585 return diff; 586 } 587 588 private String constraintSummary(ElementDefinition ed) { 589 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 590 if (ed.hasPattern()) 591 b.append("pattern=" + ed.getPattern().fhirType()); 592 if (ed.hasFixed()) 593 b.append("fixed=" + ed.getFixed().fhirType()); 594 if (ed.hasConstraint()) 595 b.append("constraints=" + ed.getConstraint().size()); 596 return b.toString(); 597 } 598 599 private String sliceSummary(ElementDefinition ed) { 600 if (!ed.hasSlicing() && !ed.hasSliceName()) 601 return ""; 602 if (ed.hasSliceName()) 603 return " (slicename = " + ed.getSliceName() + ")"; 604 605 StringBuilder b = new StringBuilder(); 606 boolean first = true; 607 for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) { 608 if (first) 609 first = false; 610 else 611 b.append("|"); 612 b.append(d.getPath()); 613 } 614 return " (slicing by " + b.toString() + ")"; 615 } 616 617 private String typeSummary(ElementDefinition ed) { 618 StringBuilder b = new StringBuilder(); 619 boolean first = true; 620 for (TypeRefComponent tr : ed.getType()) { 621 if (first) 622 first = false; 623 else 624 b.append("|"); 625 b.append(tr.getWorkingCode()); 626 } 627 return b.toString(); 628 } 629 630 private String typeSummaryWithProfile(ElementDefinition ed) { 631 StringBuilder b = new StringBuilder(); 632 boolean first = true; 633 for (TypeRefComponent tr : ed.getType()) { 634 if (first) 635 first = false; 636 else 637 b.append("|"); 638 b.append(tr.getWorkingCode()); 639 if (tr.hasProfile()) { 640 b.append("("); 641 b.append(tr.getProfile()); 642 b.append(")"); 643 644 } 645 } 646 return b.toString(); 647 } 648 649 private boolean findMatchingElement(String id, List<ElementDefinition> list) { 650 for (ElementDefinition ed : list) { 651 if (ed.getId().equals(id)) 652 return true; 653 if (id.endsWith("[x]")) { 654 if (ed.getId().startsWith(id.substring(0, id.length() - 3)) 655 && !ed.getId().substring(id.length() - 3).contains(".")) 656 return true; 657 } 658 } 659 return false; 660 } 661 662 /** 663 * @param trimDifferential 664 * @param srcSD 665 * @throws DefinitionException, FHIRException 666 * @throws Exception 667 */ 668 private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, 669 StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, 670 int diffCursor, int baseLimit, int diffLimit, String url, String webUrl, String profileName, 671 String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, 672 boolean slicingDone, List<ElementRedirection> redirector, StructureDefinition srcSD) 673 throws FHIRException { 674 675 log.debug(indent + "PP @ " + resultPathBase + " / " + contextPathSrc + " : base = " + baseCursor + " to " 676 + baseLimit + ", diff = " + diffCursor + " to " + diffLimit + " (slicing = " + slicingDone + ", redirector = " 677 + (redirector == null ? "null" : redirector.toString()) + ")"); 678 ElementDefinition res = null; 679 List<TypeSlice> typeList = new ArrayList<>(); 680 // just repeat processing entries until we run out of our allowed scope (1st 681 // entry, the allowed scope is all the entries) 682 while (baseCursor <= baseLimit) { 683 // get the current focus of the base, and decide what to do 684 ElementDefinition currentBase = base.getElement().get(baseCursor); 685 String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector); 686 687 log.debug(indent + " - " + cpath + ": base = " + baseCursor + " (" 688 + descED(base.getElement(), baseCursor) + ") to " + baseLimit + " (" + descED(base.getElement(), baseLimit) 689 + "), diff = " + diffCursor + " (" + descED(differential.getElement(), diffCursor) + ") to " + diffLimit 690 + " (" + descED(differential.getElement(), diffLimit) + ") " + "(slicingDone = " + slicingDone 691 + ") (diffpath= " 692 + (differential.getElement().size() > diffCursor ? differential.getElement().get(diffCursor).getPath() 693 : "n/a") 694 + ")"); 695 List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get 696 // a 697 // list 698 // of 699 // matching 700 // elements 701 // in 702 // scope 703 704 // in the simple case, source is not sliced. 705 if (!currentBase.hasSlicing()) { 706 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 707 // so we just copy it in 708 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 709 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 710 updateFromBase(outcome, currentBase); 711 markDerived(outcome); 712 if (resultPathBase == null) 713 resultPathBase = outcome.getPath(); 714 else if (!outcome.getPath().startsWith(resultPathBase)) 715 throw new DefinitionException("Adding wrong path"); 716 result.getElement().add(outcome); 717 if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), true)) { 718 // well, the profile walks into this, so we need to as well 719 // did we implicitly step into a new type? 720 if (baseHasChildren(base, currentBase)) { // not a new type here 721 processPaths(indent + " ", result, base, differential, baseCursor + 1, diffCursor, baseLimit, diffLimit, 722 url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, 723 resultPathBase, false, redirector, srcSD); 724 baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor + 1, baseLimit); 725 } else { 726 if (outcome.getType().size() == 0) { 727 throw new DefinitionException(diffMatches.get(0).getPath() + " has no children (" 728 + differential.getElement().get(diffCursor).getPath() + ") and no types in profile " + profileName); 729 } 730 if (outcome.getType().size() > 1) { 731 for (TypeRefComponent t : outcome.getType()) { 732 if (!t.getWorkingCode().equals("Reference")) 733 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 734 + differential.getElement().get(diffCursor).getPath() + ") and multiple types (" 735 + typeCode(outcome.getType()) + ") in profile " + profileName); 736 } 737 } 738 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 739 if (dt == null) 740 throw new DefinitionException( 741 "Unknown type " + outcome.getType().get(0) + " at " + diffMatches.get(0).getPath()); 742 contextName = dt.getUrl(); 743 int start = diffCursor; 744 while (differential.getElement().size() > diffCursor 745 && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath + ".")) 746 diffCursor++; 747 processPaths(indent + " ", result, dt.getSnapshot(), differential, 748 1 /* starting again on the data type, but skip the root */, start, 749 dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, webUrl, profileName, cpath, 750 outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD); 751 } 752 } 753 baseCursor++; 754 } else if (diffMatches.size() == 1 755 && (slicingDone || (!isImplicitSlicing(diffMatches.get(0), cpath) && !(diffMatches.get(0).hasSlicing() 756 || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName()))))) {// one matching element 757 // in the differential 758 ElementDefinition template = null; 759 if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 760 && diffMatches.get(0).getType().get(0).hasProfile() 761 && !"Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode())) { 762 CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0); 763 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue()); 764 if (sd != null) { 765 if (!sd.hasSnapshot()) { 766 StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 767 if (sdb == null) 768 throw new DefinitionException("no base for " + sd.getBaseDefinition()); 769 generateSnapshot(sdb, sd, sd.getUrl(), 770 (sdb.hasUserData("path")) ? Utilities.extractBaseUrl(sdb.getUserString("path")) : webUrl, 771 sd.getName()); 772 } 773 ElementDefinition src; 774 if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) { 775 src = null; 776 String eid = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT); 777 for (ElementDefinition t : sd.getSnapshot().getElement()) { 778 if (eid.equals(t.getId())) 779 src = t; 780 } 781 if (src == null) 782 throw new DefinitionException("Unable to find element " + eid + " in " + p.getValue()); 783 } else 784 src = sd.getSnapshot().getElement().get(0); 785 template = src.copy().setPath(currentBase.getPath()); 786 template.setSliceName(null); 787 // temporary work around 788 if (!"Extension".equals(diffMatches.get(0).getType().get(0).getCode())) { 789 template.setMin(currentBase.getMin()); 790 template.setMax(currentBase.getMax()); 791 } 792 } 793 } 794 if (template == null) 795 template = currentBase.copy(); 796 else 797 // some of what's in currentBase overrides template 798 template = overWriteWithCurrent(template, currentBase); 799 800 ElementDefinition outcome = updateURLs(url, webUrl, template); 801 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 802 if (res == null) 803 res = outcome; 804 updateFromBase(outcome, currentBase); 805 if (diffMatches.get(0).hasSliceName()) 806 outcome.setSliceName(diffMatches.get(0).getSliceName()); 807 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD); 808// if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*") && !diffMatches.get(0).hasSlicing()) // if the base profile allows multiple types, but the profile only allows one, rename it 809// outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode())); 810 outcome.setSlicing(null); 811 if (resultPathBase == null) 812 resultPathBase = outcome.getPath(); 813 else if (!outcome.getPath().startsWith(resultPathBase)) 814 throw new DefinitionException("Adding wrong path"); 815 result.getElement().add(outcome); 816 baseCursor++; 817 diffCursor = differential.getElement().indexOf(diffMatches.get(0)) + 1; 818 if (differential.getElement().size() > diffCursor && outcome.getPath().contains(".") 819 && (isDataType(outcome.getType()) || outcome.hasContentReference())) { // don't want to do this for the 820 // root, since that's base, and 821 // we're already processing it 822 if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".") 823 && !baseWalksInto(base.getElement(), baseCursor)) { 824 if (outcome.getType().size() > 1) { 825 if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) { 826 String en = tail(outcome.getPath()); 827 String tn = tail(diffMatches.get(0).getPath()); 828 String t = tn.substring(en.length() - 3); 829 if (isPrimitive(Utilities.uncapitalize(t))) 830 t = Utilities.uncapitalize(t); 831 List<TypeRefComponent> ntr = getByTypeName(outcome.getType(), t); // keep any additional information 832 if (ntr.isEmpty()) 833 ntr.add(new TypeRefComponent().setCode(t)); 834 outcome.getType().clear(); 835 outcome.getType().addAll(ntr); 836 } 837 if (outcome.getType().size() > 1) 838 for (TypeRefComponent t : outcome.getType()) { 839 if (!t.getCode().equals("Reference")) { 840 boolean nonExtension = false; 841 for (ElementDefinition ed : diffMatches) 842 if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension")) 843 nonExtension = true; 844 if (nonExtension) 845 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 846 + differential.getElement().get(diffCursor).getPath() + ") and multiple types (" 847 + typeCode(outcome.getType()) + ") in profile " + profileName); 848 } 849 } 850 } 851 int start = diffCursor; 852 while (differential.getElement().size() > diffCursor && pathStartsWith( 853 differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) 854 diffCursor++; 855 if (outcome.hasContentReference()) { 856 ElementDefinition tgt = getElementById(base.getElement(), outcome.getContentReference()); 857 if (tgt == null) 858 throw new DefinitionException("Unable to resolve reference to " + outcome.getContentReference()); 859 replaceFromContentReference(outcome, tgt); 860 int nbc = base.getElement().indexOf(tgt) + 1; 861 int nbl = nbc; 862 while (nbl < base.getElement().size() 863 && base.getElement().get(nbl).getPath().startsWith(tgt.getPath() + ".")) 864 nbl++; 865 processPaths(indent + " ", result, base, differential, nbc, start - 1, nbl - 1, diffCursor - 1, url, 866 webUrl, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, 867 resultPathBase, false, redirectorStack(redirector, outcome, cpath), srcSD); 868 } else { 869 StructureDefinition dt = outcome.getType().size() == 1 ? getProfileForDataType(outcome.getType().get(0)) 870 : getProfileForDataType("Element"); 871 if (dt == null) 872 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 873 + differential.getElement().get(diffCursor).getPath() + ") for type " 874 + typeCode(outcome.getType()) + " in profile " + profileName + ", but can't find type"); 875 contextName = dt.getUrl(); 876 processPaths(indent + " ", result, dt.getSnapshot(), differential, 877 1 /* starting again on the data type, but skip the root */, start, 878 dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, webUrl, 879 profileName + pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), 880 trimDifferential, contextName, resultPathBase, false, new ArrayList<ElementRedirection>(), srcSD); 881 } 882 } 883 } 884 } else if (diffsConstrainTypes(diffMatches, cpath, typeList)) { 885 int start = 0; 886 int nbl = findEndOfElement(base, baseCursor); 887 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 888 ElementDefinition elementToRemove = null; 889 // we come here whether they are sliced in the diff, or whether the short cut is 890 // used. 891 if (typeList.get(0).type != null) { 892 // this is the short cut method, we've just dived in and specified a type slice. 893 // in R3 (and unpatched R4, as a workaround right now... 894 if (!VersionUtilities.isR4Plus(context.getVersion()) || !newSlicingProcessing) { // newSlicingProcessing is 895 // a work around for 896 // editorial loop 897 // dependency 898 // we insert a cloned element with the right types at the start of the 899 // diffMatches 900 ElementDefinition ed = new ElementDefinition(); 901 ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath)); 902 for (TypeSlice ts : typeList) 903 ed.addType().setCode(ts.type); 904 ed.setSlicing(new ElementDefinitionSlicingComponent()); 905 ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 906 ed.getSlicing().setRules(SlicingRules.CLOSED); 907 ed.getSlicing().setOrdered(false); 908 diffMatches.add(0, ed); 909 differential.getElement().add(ndc, ed); 910 elementToRemove = ed; 911 } else { 912 // as of R4, this changed; if there's no slice, there's no constraint on the 913 // slice types, only one the type. 914 // so the element we insert specifies no types (= all types) allowed in the 915 // base, not just the listed type. 916 // see also discussion here: 917 // https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element 918 ElementDefinition ed = new ElementDefinition(); 919 ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath)); 920 ed.setSlicing(new ElementDefinitionSlicingComponent()); 921 ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 922 ed.getSlicing().setRules(SlicingRules.CLOSED); 923 ed.getSlicing().setOrdered(false); 924 diffMatches.add(0, ed); 925 differential.getElement().add(ndc, ed); 926 elementToRemove = ed; 927 } 928 } 929 int ndl = findEndOfElement(differential, ndc); 930 // the first element is setting up the slicing 931 if (diffMatches.get(0).getSlicing().hasRules()) 932 if (diffMatches.get(0).getSlicing().getRules() != SlicingRules.CLOSED) 933 throw new FHIRException( 934 "Error at path " + contextPathSrc + ": Type slicing with slicing.rules != closed"); 935 if (diffMatches.get(0).getSlicing().hasOrdered()) 936 if (diffMatches.get(0).getSlicing().getOrdered()) 937 throw new FHIRException("Error at path " + contextPathSrc + ": Type slicing with slicing.ordered = true"); 938 if (diffMatches.get(0).getSlicing().hasDiscriminator()) { 939 if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1) 940 throw new FHIRException( 941 "Error at path " + contextPathSrc + ": Type slicing with slicing.discriminator.count() > 1"); 942 if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath())) 943 throw new FHIRException( 944 "Error at path " + contextPathSrc + ": Type slicing with slicing.discriminator.path != '$this'"); 945 if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE) 946 throw new FHIRException( 947 "Error at path " + contextPathSrc + ": Type slicing with slicing.discriminator.type != 'type'"); 948 } 949 // check the slice names too while we're at it... 950 for (TypeSlice ts : typeList) 951 if (ts.type != null) { 952 String tn = rootName(cpath) + Utilities.capitalize(ts.type); 953 if (!ts.defn.hasSliceName()) 954 ts.defn.setSliceName(tn); 955 else if (!ts.defn.getSliceName().equals(tn)) 956 throw new FHIRException( 957 "Error at path " + (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath) 958 + ": Slice name must be '" + tn + "' but is '" + ts.defn.getSliceName() + "'"); 959 if (!ts.defn.hasType()) 960 ts.defn.addType().setCode(ts.type); 961 else if (ts.defn.getType().size() > 1) 962 throw new FHIRException( 963 "Error at path " + (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath) 964 + ": Slice for type '" + tn + "' has more than one type '" + ts.defn.typeSummary() + "'"); 965 else if (!ts.defn.getType().get(0).getCode().equals(ts.type)) 966 throw new FHIRException( 967 "Error at path " + (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath) 968 + ": Slice for type '" + tn + "' has wrong type '" + ts.defn.typeSummary() + "'"); 969 } 970 971 // ok passed the checks. 972 // copy the root diff, and then process any children it has 973 ElementDefinition e = processPaths(indent + " ", result, base, differential, baseCursor, ndc, nbl, ndl, url, 974 webUrl, profileName + pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, 975 contextName, resultPathBase, true, redirector, srcSD); 976 if (e == null) 977 throw new FHIRException("Did not find type root: " + diffMatches.get(0).getPath()); 978 // now set up slicing on the e (cause it was wiped by what we called. 979 e.setSlicing(new ElementDefinitionSlicingComponent()); 980 e.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 981 e.getSlicing().setRules(SlicingRules.CLOSED); 982 e.getSlicing().setOrdered(false); 983 start++; 984 // now process the siblings, which should each be type constrained - and may 985 // also have their own children 986 // now we process the base scope repeatedly for each instance of the item in the 987 // differential list 988 for (int i = start; i < diffMatches.size(); i++) { 989 // our processing scope for the differential is the item in the list, and all 990 // the items before the next one in the list 991 ndc = differential.getElement().indexOf(diffMatches.get(i)); 992 ndl = findEndOfElement(differential, ndc); 993 processPaths(indent + " ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, 994 profileName + pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, 995 resultPathBase, true, redirector, srcSD); 996 } 997 if (elementToRemove != null) { 998 differential.getElement().remove(elementToRemove); 999 ndl--; 1000 } 1001 1002 // ok, done with that - next in the base list 1003 baseCursor = nbl + 1; 1004 diffCursor = ndl + 1; 1005 1006 } else { 1007 // ok, the differential slices the item. Let's check our pre-conditions to 1008 // ensure that this is correct 1009 if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0))) 1010 // you can only slice an element that doesn't repeat if the sum total of your 1011 // slices is limited to 1 1012 // (but you might do that in order to split up constraints by type) 1013 throw new DefinitionException("Attempt to a slice an element that does not repeat: " + currentBase.getPath() 1014 + "/" + currentBase.getPath() + " from " + contextName + " in " + url); 1015 if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but 1016 // hasn't defined it. this is an error 1017 throw new DefinitionException("Differential does not have a slice: " + currentBase.getPath() + "/ (b:" 1018 + baseCursor + " of " + baseLimit + " / " + diffCursor + "/ " + diffLimit + ") in profile " + url); 1019 1020 // well, if it passed those preconditions then we slice the dest. 1021 int start = 0; 1022 int nbl = findEndOfElement(base, baseCursor); 1023// if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) { 1024 if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && (nbl > baseCursor || differential 1025 .getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0)) + 1)) { // there's 1026 // a 1027 // default 1028 // set 1029 // before 1030 // the 1031 // slices 1032 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 1033 int ndl = findEndOfElement(differential, ndc); 1034 ElementDefinition e = processPaths(indent + " ", result, base, differential, baseCursor, ndc, nbl, ndl, 1035 url, webUrl, profileName + pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, 1036 contextName, resultPathBase, true, redirector, srcSD); 1037 if (e == null) 1038 throw new FHIRException("Did not find single slice: " + diffMatches.get(0).getPath()); 1039 e.setSlicing(diffMatches.get(0).getSlicing()); 1040 start++; 1041 } else { 1042 // we're just going to accept the differential slicing at face value 1043 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 1044 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1045 updateFromBase(outcome, currentBase); 1046 1047 if (!diffMatches.get(0).hasSlicing()) 1048 outcome.setSlicing(makeExtensionSlicing()); 1049 else 1050 outcome.setSlicing(diffMatches.get(0).getSlicing().copy()); 1051 if (!outcome.getPath().startsWith(resultPathBase)) 1052 throw new DefinitionException("Adding wrong path"); 1053 result.getElement().add(outcome); 1054 1055 // differential - if the first one in the list has a name, we'll process it. 1056 // Else we'll treat it as the base definition of the slice. 1057 if (!diffMatches.get(0).hasSliceName()) { 1058 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD); 1059 if (!outcome.hasContentReference() && !outcome.hasType()) { 1060 throw new DefinitionException("not done yet"); 1061 } 1062 start++; 1063 // result.getElement().remove(result.getElement().size()-1); 1064 } else 1065 checkExtensionDoco(outcome); 1066 } 1067 // now, for each entry in the diff matches, we're going to process the base item 1068 // our processing scope for base is all the children of the current path 1069 int ndc = diffCursor; 1070 int ndl = diffCursor; 1071 for (int i = start; i < diffMatches.size(); i++) { 1072 // our processing scope for the differential is the item in the list, and all 1073 // the items before the next one in the list 1074 ndc = differential.getElement().indexOf(diffMatches.get(i)); 1075 ndl = findEndOfElement(differential, ndc); 1076 /* 1077 * if (skipSlicingElement && i == 0) { ndc = ndc + 1; if (ndc > ndl) continue; } 1078 */ 1079 // now we process the base scope repeatedly for each instance of the item in the 1080 // differential list 1081 processPaths(indent + " ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, 1082 profileName + pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, 1083 resultPathBase, true, redirector, srcSD); 1084 } 1085 // ok, done with that - next in the base list 1086 baseCursor = nbl + 1; 1087 diffCursor = ndl + 1; 1088 } 1089 } else { 1090 // the item is already sliced in the base profile. 1091 // here's the rules 1092 // 1. irrespective of whether the slicing is ordered or not, the definition 1093 // order must be maintained 1094 // 2. slice element names have to match. 1095 // 3. new slices must be introduced at the end 1096 // corallory: you can't re-slice existing slices. is that ok? 1097 1098 // we're going to need this: 1099 String path = currentBase.getPath(); 1100 ElementDefinition original = currentBase; 1101 1102 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 1103 // copy across the currentbase, and all of its children and siblings 1104 while (baseCursor < base.getElement().size() 1105 && base.getElement().get(baseCursor).getPath().startsWith(path)) { 1106 ElementDefinition outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy()); 1107 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1108 if (!outcome.getPath().startsWith(resultPathBase)) 1109 throw new DefinitionException( 1110 "Adding wrong path in profile " + profileName + ": " + outcome.getPath() + " vs " + resultPathBase); 1111 result.getElement().add(outcome); // so we just copy it in 1112 baseCursor++; 1113 } 1114 } else { 1115 // first - check that the slicing is ok 1116 boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED; 1117 int diffpos = 0; 1118 boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension"); 1119 if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything 1120 // about slicing 1121// if (!isExtension) 1122// diffpos++; // if there's a slice on the first, we'll ignore any content it has 1123 ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing(); 1124 ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing(); 1125 if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() 1126 && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement())) 1127 throw new DefinitionException( 1128 "Slicing rules on differential (" + summarizeSlicing(dSlice) + ") do not match those on base (" 1129 + summarizeSlicing(bSlice) + ") - order @ " + path + " (" + contextName + ")"); 1130 if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator())) 1131 throw new DefinitionException( 1132 "Slicing rules on differential (" + summarizeSlicing(dSlice) + ") do not match those on base (" 1133 + summarizeSlicing(bSlice) + ") - discriminator @ " + path + " (" + contextName + ")"); 1134 if (!ruleMatches(dSlice.getRules(), bSlice.getRules())) 1135 throw new DefinitionException( 1136 "Slicing rules on differential (" + summarizeSlicing(dSlice) + ") do not match those on base (" 1137 + summarizeSlicing(bSlice) + ") - rule @ " + path + " (" + contextName + ")"); 1138 } 1139 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 1140 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1141 updateFromBase(outcome, currentBase); 1142 if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) { 1143 updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing()); 1144 updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url, srcSD); // if there's no slice, 1145 // we don't want to 1146 // update the unsliced 1147 // description 1148 } else if (!diffMatches.get(0).hasSliceName()) 1149 diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't 1150 // called 1151 1152 result.getElement().add(outcome); 1153 1154 if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice 1155 diffpos++; 1156 } 1157 if (hasInnerDiffMatches(differential, cpath, diffpos, diffLimit, base.getElement(), false)) { 1158 int nbl = findEndOfElement(base, baseCursor); 1159 int ndc = differential.getElement().indexOf(diffMatches.get(0)) + 1; 1160 int ndl = findEndOfElement(differential, ndc); 1161 processPaths(indent + " ", result, base, differential, baseCursor + 1, ndc, nbl, ndl, url, webUrl, 1162 profileName + pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, 1163 resultPathBase, false, null, srcSD); 1164// throw new Error("Not done yet"); 1165// } else if (currentBase.getType().get(0).getCode().equals("BackboneElement") && diffMatches.size() > 0 && diffMatches.get(0).hasSliceName()) { 1166 } else if (currentBase.getType().get(0).getCode().equals("BackboneElement")) { 1167 // We need to copy children of the backbone element before we start messing 1168 // around with slices 1169 int nbl = findEndOfElement(base, baseCursor); 1170 for (int i = baseCursor + 1; i <= nbl; i++) { 1171 outcome = updateURLs(url, webUrl, base.getElement().get(i).copy()); 1172 result.getElement().add(outcome); 1173 } 1174 } 1175 1176 // now, we have two lists, base and diff. we're going to work through base, 1177 // looking for matches in diff. 1178 List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase); 1179 for (ElementDefinition baseItem : baseMatches) { 1180 baseCursor = base.getElement().indexOf(baseItem); 1181 outcome = updateURLs(url, webUrl, baseItem.copy()); 1182 updateFromBase(outcome, currentBase); 1183 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1184 outcome.setSlicing(null); 1185 if (!outcome.getPath().startsWith(resultPathBase)) 1186 throw new DefinitionException("Adding wrong path"); 1187 if (diffpos < diffMatches.size() 1188 && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) { 1189 // if there's a diff, we update the outcome with diff 1190 // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, 1191 // closed, url); 1192 // then process any children 1193 int nbl = findEndOfElement(base, baseCursor); 1194 int ndc = differential.getElement().indexOf(diffMatches.get(diffpos)); 1195 int ndl = findEndOfElement(differential, ndc); 1196 // now we process the base scope repeatedly for each instance of the item in the 1197 // differential list 1198 processPaths(indent + " ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, 1199 profileName + pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, 1200 resultPathBase, true, redirector, srcSD); 1201 // ok, done with that - now set the cursors for if this is the end 1202 baseCursor = nbl; 1203 diffCursor = ndl + 1; 1204 diffpos++; 1205 } else { 1206 result.getElement().add(outcome); 1207 baseCursor++; 1208 // just copy any children on the base 1209 while (baseCursor < base.getElement().size() 1210 && base.getElement().get(baseCursor).getPath().startsWith(path) 1211 && !base.getElement().get(baseCursor).getPath().equals(path)) { 1212 outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy()); 1213 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1214 if (!outcome.getPath().startsWith(resultPathBase)) 1215 throw new DefinitionException("Adding wrong path"); 1216 result.getElement().add(outcome); 1217 baseCursor++; 1218 } 1219 // Lloyd - add this for test T15 1220 baseCursor--; 1221 } 1222 } 1223 // finally, we process any remaining entries in diff, which are new (and which 1224 // are only allowed if the base wasn't closed 1225 if (closed && diffpos < diffMatches.size()) 1226 throw new DefinitionException( 1227 "The base snapshot marks a slicing as closed, but the differential tries to extend it in " + profileName 1228 + " at " + path + " (" + cpath + ")"); 1229 if (diffpos == diffMatches.size()) { 1230//Lloyd This was causing problems w/ Telus 1231// diffCursor++; 1232 } else { 1233 while (diffpos < diffMatches.size()) { 1234 ElementDefinition diffItem = diffMatches.get(diffpos); 1235 for (ElementDefinition baseItem : baseMatches) 1236 if (baseItem.getSliceName().equals(diffItem.getSliceName())) 1237 throw new DefinitionException("Named items are out of order in the slice"); 1238 outcome = updateURLs(url, webUrl, currentBase.copy()); 1239 // outcome = updateURLs(url, diffItem.copy()); 1240 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1241 updateFromBase(outcome, currentBase); 1242 outcome.setSlicing(null); 1243 if (!outcome.getPath().startsWith(resultPathBase)) 1244 throw new DefinitionException("Adding wrong path"); 1245 result.getElement().add(outcome); 1246 updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url, srcSD); 1247 // --- LM Added this 1248 diffCursor = differential.getElement().indexOf(diffItem) + 1; 1249 if (!outcome.getType().isEmpty() 1250 && (/* outcome.getType().get(0).getCode().equals("Extension") || */differential.getElement() 1251 .size() > diffCursor) 1252 && outcome.getPath().contains(".") && isDataType(outcome.getType())) { // don't want to do this for 1253 // the root, since that's base, 1254 // and we're already processing 1255 // it 1256 if (!baseWalksInto(base.getElement(), baseCursor)) { 1257 if (differential.getElement().size() > diffCursor && pathStartsWith( 1258 differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) { 1259 if (outcome.getType().size() > 1) 1260 for (TypeRefComponent t : outcome.getType()) { 1261 if (!t.getCode().equals("Reference")) 1262 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 1263 + differential.getElement().get(diffCursor).getPath() + ") and multiple types (" 1264 + typeCode(outcome.getType()) + ") in profile " + profileName); 1265 } 1266 TypeRefComponent t = outcome.getType().get(0); 1267 if (t.getCode().equals("BackboneElement")) { 1268 int baseStart = base.getElement().indexOf(currentBase) + 1; 1269 int baseMax = baseStart + 1; 1270 while (baseMax < base.getElement().size() 1271 && base.getElement().get(baseMax).getPath().startsWith(currentBase.getPath() + ".")) 1272 baseMax++; 1273 int start = diffCursor; 1274 while (differential.getElement().size() > diffCursor && pathStartsWith( 1275 differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) 1276 diffCursor++; 1277 processPaths(indent + " ", result, base, differential, baseStart, start - 1, baseMax - 1, 1278 diffCursor - 1, url, webUrl, profileName + pathTail(diffMatches, 0), 1279 base.getElement().get(0).getPath(), base.getElement().get(0).getPath(), trimDifferential, 1280 contextName, resultPathBase, false, redirector, srcSD); 1281 1282 } else { 1283 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 1284 // if (t.getCode().equals("Extension") && t.hasProfile() && 1285 // !t.getProfile().contains(":")) { 1286 // lloydfix dt = 1287 // } 1288 if (dt == null) 1289 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 1290 + differential.getElement().get(diffCursor).getPath() + ") for type " 1291 + typeCode(outcome.getType()) + " in profile " + profileName + ", but can't find type"); 1292 contextName = dt.getUrl(); 1293 int start = diffCursor; 1294 while (differential.getElement().size() > diffCursor && pathStartsWith( 1295 differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) 1296 diffCursor++; 1297 processPaths(indent + " ", result, dt.getSnapshot(), differential, 1298 1 /* starting again on the data type, but skip the root */, start - 1, 1299 dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, webUrl, 1300 profileName + pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), 1301 trimDifferential, contextName, resultPathBase, false, redirector, srcSD); 1302 } 1303 } else if (outcome.getType().get(0).getCode().equals("Extension")) { 1304 // Force URL to appear if we're dealing with an extension. (This is a kludge - 1305 // may need to drill down in other cases where we're slicing and the type has a 1306 // profile declaration that could be setting the fixed value) 1307 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 1308 for (ElementDefinition extEd : dt.getSnapshot().getElement()) { 1309 // We only want the children that aren't the root 1310 if (extEd.getPath().contains(".")) { 1311 ElementDefinition extUrlEd = updateURLs(url, webUrl, extEd.copy()); 1312 extUrlEd.setPath(fixedPathDest(outcome.getPath(), extUrlEd.getPath(), redirector, null)); 1313 // updateFromBase(extUrlEd, currentBase); 1314 markDerived(extUrlEd); 1315 result.getElement().add(extUrlEd); 1316 } 1317 } 1318 } 1319 } 1320 } 1321 // --- 1322 diffpos++; 1323 } 1324 } 1325 baseCursor++; 1326 } 1327 } 1328 } 1329 1330 int i = 0; 1331 for (ElementDefinition e : result.getElement()) { 1332 i++; 1333 if (e.hasMinElement() && e.getMinElement().getValue() == null) 1334 throw new Error("null min"); 1335 } 1336 return res; 1337 } 1338 1339 private String descED(List<ElementDefinition> list, int index) { 1340 return index >= 0 && index < list.size() ? list.get(index).present() : "X"; 1341 } 1342 1343 private boolean baseHasChildren(StructureDefinitionSnapshotComponent base, ElementDefinition ed) { 1344 int index = base.getElement().indexOf(ed); 1345 if (index == -1 || index >= base.getElement().size() - 1) 1346 return false; 1347 String p = base.getElement().get(index + 1).getPath(); 1348 return isChildOf(p, ed.getPath()); 1349 } 1350 1351 private boolean isChildOf(String sub, String focus) { 1352 if (focus.endsWith("[x]")) { 1353 focus = focus.substring(0, focus.length() - 3); 1354 return sub.startsWith(focus); 1355 } else 1356 return sub.startsWith(focus + "."); 1357 } 1358 1359 private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, 1360 int baseLimit) { 1361 return baseLimit + 1; 1362 } 1363 1364 private String rootName(String cpath) { 1365 String t = tail(cpath); 1366 return t.replace("[x]", ""); 1367 } 1368 1369 private String determineTypeSlicePath(String path, String cpath) { 1370 String headP = path.substring(0, path.lastIndexOf(".")); 1371// String tailP = path.substring(path.lastIndexOf(".")+1); 1372 String tailC = cpath.substring(cpath.lastIndexOf(".") + 1); 1373 return headP + "." + tailC; 1374 } 1375 1376 private boolean isImplicitSlicing(ElementDefinition ed, String path) { 1377 if (ed == null || ed.getPath() == null || path == null) 1378 return false; 1379 if (path.equals(ed.getPath())) 1380 return false; 1381 boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length() - 3)); 1382 return ok; 1383 } 1384 1385 private boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) { 1386// if (diffMatches.size() < 2) 1387// return false; 1388 String p = diffMatches.get(0).getPath(); 1389 if (!p.endsWith("[x]") && !cPath.endsWith("[x]")) 1390 return false; 1391 typeList.clear(); 1392 String rn = tail(cPath); 1393 rn = rn.substring(0, rn.length() - 3); 1394 for (int i = 0; i < diffMatches.size(); i++) { 1395 ElementDefinition ed = diffMatches.get(i); 1396 String n = tail(ed.getPath()); 1397 if (!n.startsWith(rn)) 1398 return false; 1399 String s = n.substring(rn.length()); 1400 if (!s.contains(".")) { 1401 if (ed.hasSliceName() && ed.getType().size() == 1) { 1402 typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode())); 1403 } else if (!ed.hasSliceName() && !s.equals("[x]")) { 1404 if (isDataType(s)) 1405 typeList.add(new TypeSlice(ed, s)); 1406 else if (isConstrainedDataType(s)) 1407 typeList.add(new TypeSlice(ed, baseType(s))); 1408 else if (isPrimitive(Utilities.uncapitalize(s))) 1409 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 1410 } else if (!ed.hasSliceName() && s.equals("[x]")) 1411 typeList.add(new TypeSlice(ed, null)); 1412 } 1413 } 1414 return true; 1415 } 1416 1417 private List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, 1418 String path) { 1419 List<ElementRedirection> result = new ArrayList<ElementRedirection>(); 1420 result.addAll(redirector); 1421 result.add(new ElementRedirection(outcome, path)); 1422 return result; 1423 } 1424 1425 private List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) { 1426 List<TypeRefComponent> res = new ArrayList<TypeRefComponent>(); 1427 for (TypeRefComponent tr : type) { 1428 if (t.equals(tr.getWorkingCode())) 1429 res.add(tr); 1430 } 1431 return res; 1432 } 1433 1434 private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) { 1435 outcome.setContentReference(null); 1436 outcome.getType().clear(); // though it should be clear anyway 1437 outcome.getType().addAll(tgt.getType()); 1438 } 1439 1440 private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) { 1441 if (cursor >= elements.size()) 1442 return false; 1443 String path = elements.get(cursor).getPath(); 1444 String prevPath = elements.get(cursor - 1).getPath(); 1445 return path.startsWith(prevPath + "."); 1446 } 1447 1448 private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) 1449 throws FHIRFormatError { 1450 ElementDefinition res = profile.copy(); 1451 if (usage.hasSliceName()) 1452 res.setSliceName(usage.getSliceName()); 1453 if (usage.hasLabel()) 1454 res.setLabel(usage.getLabel()); 1455 for (Coding c : usage.getCode()) 1456 res.addCode(c); 1457 1458 if (usage.hasDefinition()) 1459 res.setDefinition(usage.getDefinition()); 1460 if (usage.hasShort()) 1461 res.setShort(usage.getShort()); 1462 if (usage.hasComment()) 1463 res.setComment(usage.getComment()); 1464 if (usage.hasRequirements()) 1465 res.setRequirements(usage.getRequirements()); 1466 for (StringType c : usage.getAlias()) 1467 res.addAlias(c.getValue()); 1468 if (usage.hasMin()) 1469 res.setMin(usage.getMin()); 1470 if (usage.hasMax()) 1471 res.setMax(usage.getMax()); 1472 1473 if (usage.hasFixed()) 1474 res.setFixed(usage.getFixed()); 1475 if (usage.hasPattern()) 1476 res.setPattern(usage.getPattern()); 1477 if (usage.hasExample()) 1478 res.setExample(usage.getExample()); 1479 if (usage.hasMinValue()) 1480 res.setMinValue(usage.getMinValue()); 1481 if (usage.hasMaxValue()) 1482 res.setMaxValue(usage.getMaxValue()); 1483 if (usage.hasMaxLength()) 1484 res.setMaxLength(usage.getMaxLength()); 1485 if (usage.hasMustSupport()) 1486 res.setMustSupport(usage.getMustSupport()); 1487 if (usage.hasBinding()) 1488 res.setBinding(usage.getBinding().copy()); 1489 for (ElementDefinitionConstraintComponent c : usage.getConstraint()) 1490 res.addConstraint(c); 1491 for (Extension e : usage.getExtension()) { 1492 if (!res.hasExtension(e.getUrl())) 1493 res.addExtension(e.copy()); 1494 } 1495 1496 return res; 1497 } 1498 1499 private boolean checkExtensionDoco(ElementDefinition base) { 1500 // see task 3970. For an extension, there's no point copying across all the 1501 // underlying definitional stuff 1502 boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") 1503 || base.getPath().endsWith(".modifierExtension"); 1504 if (isExtension) { 1505 base.setDefinition("An Extension"); 1506 base.setShort("Extension"); 1507 base.setCommentElement(null); 1508 base.setRequirementsElement(null); 1509 base.getAlias().clear(); 1510 base.getMapping().clear(); 1511 } 1512 return isExtension; 1513 } 1514 1515 private String pathTail(List<ElementDefinition> diffMatches, int i) { 1516 1517 ElementDefinition d = diffMatches.get(i); 1518 String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".") + 1) : d.getPath(); 1519 return "." + s 1520 + (d.hasType() && d.getType().get(0).hasProfile() ? "[" + d.getType().get(0).getProfile() + "]" : ""); 1521 } 1522 1523 private void markDerived(ElementDefinition outcome) { 1524 for (ElementDefinitionConstraintComponent inv : outcome.getConstraint()) 1525 inv.setUserData(IS_DERIVED, true); 1526 } 1527 1528 private String summarizeSlicing(ElementDefinitionSlicingComponent slice) { 1529 StringBuilder b = new StringBuilder(); 1530 boolean first = true; 1531 for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) { 1532 if (first) 1533 first = false; 1534 else 1535 b.append(", "); 1536 b.append(d); 1537 } 1538 b.append("("); 1539 if (slice.hasOrdered()) 1540 b.append(slice.getOrderedElement().asStringValue()); 1541 b.append("/"); 1542 if (slice.hasRules()) 1543 b.append(slice.getRules().toCode()); 1544 b.append(")"); 1545 if (slice.hasDescription()) { 1546 b.append(" \""); 1547 b.append(slice.getDescription()); 1548 b.append("\""); 1549 } 1550 return b.toString(); 1551 } 1552 1553 private void updateFromBase(ElementDefinition derived, ElementDefinition base) { 1554 if (base.hasBase()) { 1555 if (!derived.hasBase()) 1556 derived.setBase(new ElementDefinitionBaseComponent()); 1557 derived.getBase().setPath(base.getBase().getPath()); 1558 derived.getBase().setMin(base.getBase().getMin()); 1559 derived.getBase().setMax(base.getBase().getMax()); 1560 } else { 1561 if (!derived.hasBase()) 1562 derived.setBase(new ElementDefinitionBaseComponent()); 1563 derived.getBase().setPath(base.getPath()); 1564 derived.getBase().setMin(base.getMin()); 1565 derived.getBase().setMax(base.getMax()); 1566 } 1567 } 1568 1569 private boolean pathStartsWith(String p1, String p2) { 1570 return p1.startsWith(p2); 1571 } 1572 1573 private boolean pathMatches(String p1, String p2) { 1574 return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length() - 3)) 1575 && !p1.substring(p2.length() - 3).contains(".")); 1576 } 1577 1578 private String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) { 1579 if (contextPath == null) 1580 return pathSimple; 1581// String ptail = pathSimple.substring(contextPath.length() + 1); 1582 if (redirector.size() > 0) { 1583 String ptail = pathSimple.substring(contextPath.length() + 1); 1584 return redirector.get(redirector.size() - 1).getPath() + "." + ptail; 1585// return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1); 1586 } else { 1587 String ptail = pathSimple.substring(pathSimple.indexOf(".") + 1); 1588 return contextPath + "." + ptail; 1589 } 1590 } 1591 1592 private String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, 1593 String redirectSource) { 1594 String s; 1595 if (contextPath == null) 1596 s = pathSimple; 1597 else { 1598 if (redirector.size() > 0) { 1599 String ptail = pathSimple.substring(redirectSource.length() + 1); 1600 // ptail = ptail.substring(ptail.indexOf(".")+1); 1601 s = contextPath + "." + /* tail(redirector.getPath())+"."+ */ptail; 1602 } else { 1603 String ptail = pathSimple.substring(pathSimple.indexOf(".") + 1); 1604 s = contextPath + "." + ptail; 1605 } 1606 } 1607 return s; 1608 } 1609 1610 private StructureDefinition getProfileForDataType(TypeRefComponent type) { 1611 StructureDefinition sd = null; 1612 if (type.hasProfile()) { 1613 sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue()); 1614 if (sd == null) 1615 log.warn("Failed to find referenced profile: " + type.getProfile()); 1616 } 1617 if (sd == null) 1618 sd = context.fetchTypeDefinition(type.getWorkingCode()); 1619 if (sd == null) 1620 log.warn("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM 1621 return sd; 1622 } 1623 1624 private StructureDefinition getProfileForDataType(String type) { 1625 StructureDefinition sd = context.fetchTypeDefinition(type); 1626 if (sd == null) 1627 log.warn("XX: failed to find profile for type: " + type); // debug GJM 1628 return sd; 1629 } 1630 1631 public static String typeCode(List<TypeRefComponent> types) { 1632 StringBuilder b = new StringBuilder(); 1633 boolean first = true; 1634 for (TypeRefComponent type : types) { 1635 if (first) 1636 first = false; 1637 else 1638 b.append(", "); 1639 b.append(type.getWorkingCode()); 1640 if (type.hasTargetProfile()) 1641 b.append("{" + type.getTargetProfile() + "}"); 1642 else if (type.hasProfile()) 1643 b.append("{" + type.getProfile() + "}"); 1644 } 1645 return b.toString(); 1646 } 1647 1648 private boolean isDataType(List<TypeRefComponent> types) { 1649 if (types.isEmpty()) 1650 return false; 1651 for (TypeRefComponent type : types) { 1652 String t = type.getWorkingCode(); 1653 if (!isDataType(t) && !isPrimitive(t)) 1654 return false; 1655 } 1656 return true; 1657 } 1658 1659 /** 1660 * Finds internal references in an Element's Binding and StructureDefinition 1661 * references (in TypeRef) and bases them on the given url 1662 * 1663 * @param url - the base url to use to turn internal references into 1664 * absolute references 1665 * @param element - the Element to update 1666 * @return - the updated Element 1667 */ 1668 private ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) { 1669 if (element != null) { 1670 ElementDefinition defn = element; 1671 if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#")) 1672 defn.getBinding().setValueSet(url + defn.getBinding().getValueSet()); 1673 for (TypeRefComponent t : defn.getType()) { 1674 for (UriType u : t.getProfile()) { 1675 if (u.getValue().startsWith("#")) 1676 u.setValue(url + t.getProfile()); 1677 } 1678 for (UriType u : t.getTargetProfile()) { 1679 if (u.getValue().startsWith("#")) 1680 u.setValue(url + t.getTargetProfile()); 1681 } 1682 } 1683 if (webUrl != null) { 1684 // also, must touch up the markdown 1685 if (element.hasDefinition()) 1686 element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl)); 1687 if (element.hasComment()) 1688 element.setComment(processRelativeUrls(element.getComment(), webUrl)); 1689 if (element.hasRequirements()) 1690 element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl)); 1691 if (element.hasMeaningWhenMissing()) 1692 element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl)); 1693 } 1694 } 1695 return element; 1696 } 1697 1698 private String processRelativeUrls(String markdown, String webUrl) { 1699 StringBuilder b = new StringBuilder(); 1700 int i = 0; 1701 while (i < markdown.length()) { 1702 if (i < markdown.length() - 3 && markdown.substring(i, i + 2).equals("](")) { 1703 int j = i + 2; 1704 while (j < markdown.length() && markdown.charAt(j) != ')') 1705 j++; 1706 if (j < markdown.length()) { 1707 String url = markdown.substring(i + 2, j); 1708 if (!Utilities.isAbsoluteUrl(url)) { 1709 b.append("]("); 1710 b.append(webUrl); 1711 i = i + 1; 1712 } else 1713 b.append(markdown.charAt(i)); 1714 } else 1715 b.append(markdown.charAt(i)); 1716 } else { 1717 b.append(markdown.charAt(i)); 1718 } 1719 i++; 1720 } 1721 return b.toString(); 1722 } 1723 1724 private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) { 1725 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1726 String path = current.getPath(); 1727 int cursor = list.indexOf(current) + 1; 1728 while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) { 1729 if (pathMatches(list.get(cursor).getPath(), path)) 1730 result.add(list.get(cursor)); 1731 cursor++; 1732 } 1733 return result; 1734 } 1735 1736 private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) { 1737 if (src.hasOrderedElement()) 1738 dst.setOrderedElement(src.getOrderedElement().copy()); 1739 if (src.hasDiscriminator()) { 1740 // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll 1741 // because it uses object equality, not string equality 1742 for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) { 1743 boolean found = false; 1744 for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) { 1745 if (matches(d, s)) { 1746 found = true; 1747 break; 1748 } 1749 } 1750 if (!found) 1751 dst.getDiscriminator().add(s); 1752 } 1753 } 1754 if (src.hasRulesElement()) 1755 dst.setRulesElement(src.getRulesElement().copy()); 1756 } 1757 1758 private boolean orderMatches(BooleanType diff, BooleanType base) { 1759 return (diff == null) || (base == null) || (diff.getValue() == base.getValue()); 1760 } 1761 1762 private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, 1763 List<ElementDefinitionSlicingDiscriminatorComponent> base) { 1764 if (diff.isEmpty() || base.isEmpty()) 1765 return true; 1766 if (diff.size() != base.size()) 1767 return false; 1768 for (int i = 0; i < diff.size(); i++) 1769 if (!matches(diff.get(i), base.get(i))) 1770 return false; 1771 return true; 1772 } 1773 1774 private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, 1775 ElementDefinitionSlicingDiscriminatorComponent c2) { 1776 return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath()); 1777 } 1778 1779 private boolean ruleMatches(SlicingRules diff, SlicingRules base) { 1780 return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) 1781 || ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED)); 1782 } 1783 1784 private boolean isSlicedToOneOnly(ElementDefinition e) { 1785 return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); 1786 } 1787 1788 private ElementDefinitionSlicingComponent makeExtensionSlicing() { 1789 ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); 1790 slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); 1791 slice.setOrdered(false); 1792 slice.setRules(SlicingRules.OPEN); 1793 return slice; 1794 } 1795 1796 private boolean isExtension(ElementDefinition currentBase) { 1797 return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension"); 1798 } 1799 1800 private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, 1801 List<ElementDefinition> base, boolean allowSlices) throws DefinitionException { 1802 end = Math.min(context.getElement().size(), end); 1803 start = Math.max(0, start); 1804 1805 for (int i = start; i <= end; i++) { 1806 String statedPath = context.getElement().get(i).getPath(); 1807 if (statedPath.startsWith(path + ".")) { 1808 return true; 1809 } else if (!statedPath.endsWith(path)) 1810 break; 1811 } 1812 return false; 1813 } 1814 1815 private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, 1816 int start, int end, String profileName) throws DefinitionException { 1817 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1818 for (int i = start; i <= end; i++) { 1819 String statedPath = context.getElement().get(i).getPath(); 1820 if (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 1821 && statedPath.substring(0, path.length() - 3).equals(path.substring(0, path.length() - 3)) 1822 && (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains(".")))) { 1823 /* 1824 * Commenting this out because it raises warnings when profiling inherited 1825 * elements. For example, Error: unknown element 'Bundle.meta.profile' (or it is 1826 * out of order) in profile ... (looking for 'Bundle.entry') Not sure we have 1827 * enough information here to do the check properly. Might be better done when 1828 * we're sorting the profile? 1829 * 1830 * if (i != start && result.isEmpty() && 1831 * !path.startsWith(context.getElement().get(start).getPath())) messages.add(new 1832 * ValidationMessage(Source.ProfileValidator, IssueType.VALUE, 1833 * "StructureDefinition.differential.element["+Integer.toString(start)+"]", 1834 * "Error: unknown element '"+context.getElement().get(start).getPath() 1835 * +"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')", 1836 * IssueSeverity.WARNING)); 1837 * 1838 */ 1839 result.add(context.getElement().get(i)); 1840 } 1841 } 1842 return result; 1843 } 1844 1845 private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) { 1846 int result = cursor; 1847 String path = context.getElement().get(cursor).getPath() + "."; 1848 while (result < context.getElement().size() - 1 && context.getElement().get(result + 1).getPath().startsWith(path)) 1849 result++; 1850 return result; 1851 } 1852 1853 private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) { 1854 int result = cursor; 1855 String path = context.getElement().get(cursor).getPath() + "."; 1856 while (result < context.getElement().size() - 1 && context.getElement().get(result + 1).getPath().startsWith(path)) 1857 result++; 1858 return result; 1859 } 1860 1861 private boolean unbounded(ElementDefinition definition) { 1862 StringType max = definition.getMaxElement(); 1863 if (max == null) 1864 return false; // this is not valid 1865 if (max.getValue().equals("1")) 1866 return false; 1867 if (max.getValue().equals("0")) 1868 return false; 1869 return true; 1870 } 1871 1872 private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, 1873 boolean trimDifferential, String purl, StructureDefinition srcSD) throws DefinitionException, FHIRException { 1874 source.setUserData(GENERATED_IN_SNAPSHOT, dest); 1875 // we start with a clone of the base profile ('dest') and we copy from the 1876 // profile ('source') 1877 // over the top for anything the source has 1878 ElementDefinition base = dest; 1879 ElementDefinition derived = source; 1880 derived.setUserData(DERIVATION_POINTER, base); 1881 boolean isExtension = checkExtensionDoco(base); 1882 1883 // Before applying changes, apply them to what's in the profile 1884 // TODO: follow Chris's rules - Done by Lloyd 1885 StructureDefinition profile = null; 1886 if (base.hasSliceName()) 1887 profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() 1888 ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue()) 1889 : null; 1890 if (profile == null) 1891 profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() 1892 ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue()) 1893 : null; 1894 if (profile != null) { 1895 ElementDefinition e = profile.getSnapshot().getElement().get(0); 1896 base.setDefinition(e.getDefinition()); 1897 base.setShort(e.getShort()); 1898 if (e.hasCommentElement()) 1899 base.setCommentElement(e.getCommentElement()); 1900 if (e.hasRequirementsElement()) 1901 base.setRequirementsElement(e.getRequirementsElement()); 1902 base.getAlias().clear(); 1903 base.getAlias().addAll(e.getAlias()); 1904 base.getMapping().clear(); 1905 base.getMapping().addAll(e.getMapping()); 1906 } 1907 if (derived != null) { 1908 if (derived.hasSliceName()) { 1909 base.setSliceName(derived.getSliceName()); 1910 } 1911 1912 if (derived.hasShortElement()) { 1913 if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false)) 1914 base.setShortElement(derived.getShortElement().copy()); 1915 else if (trimDifferential) 1916 derived.setShortElement(null); 1917 else if (derived.hasShortElement()) 1918 derived.getShortElement().setUserData(DERIVATION_EQUALS, true); 1919 } 1920 1921 if (derived.hasDefinitionElement()) { 1922 if (derived.getDefinition().startsWith("...")) 1923 base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition())); 1924 else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) 1925 base.setDefinitionElement(derived.getDefinitionElement().copy()); 1926 else if (trimDifferential) 1927 derived.setDefinitionElement(null); 1928 else if (derived.hasDefinitionElement()) 1929 derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true); 1930 } 1931 1932 if (derived.hasCommentElement()) { 1933 if (derived.getComment().startsWith("...")) 1934 base.setComment(Utilities.appendDerivedTextToBase(base.getComment(), derived.getComment())); 1935 else if (derived.hasCommentElement() != base.hasCommentElement() 1936 || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false)) 1937 base.setCommentElement(derived.getCommentElement().copy()); 1938 else if (trimDifferential) 1939 base.setCommentElement(derived.getCommentElement().copy()); 1940 else if (derived.hasCommentElement()) 1941 derived.getCommentElement().setUserData(DERIVATION_EQUALS, true); 1942 } 1943 1944 if (derived.hasLabelElement()) { 1945 if (derived.getLabel().startsWith("...")) 1946 base.setLabel(Utilities.appendDerivedTextToBase(base.getLabel(), derived.getLabel())); 1947 else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false)) 1948 base.setLabelElement(derived.getLabelElement().copy()); 1949 else if (trimDifferential) 1950 base.setLabelElement(derived.getLabelElement().copy()); 1951 else if (derived.hasLabelElement()) 1952 derived.getLabelElement().setUserData(DERIVATION_EQUALS, true); 1953 } 1954 1955 if (derived.hasRequirementsElement()) { 1956 if (derived.getRequirements().startsWith("...")) 1957 base.setRequirements(Utilities.appendDerivedTextToBase(base.getRequirements(), derived.getRequirements())); 1958 else if (!base.hasRequirementsElement() 1959 || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false)) 1960 base.setRequirementsElement(derived.getRequirementsElement().copy()); 1961 else if (trimDifferential) 1962 base.setRequirementsElement(derived.getRequirementsElement().copy()); 1963 else if (derived.hasRequirementsElement()) 1964 derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true); 1965 } 1966 // sdf-9 1967 if (derived.hasRequirements() && !base.getPath().contains(".")) 1968 derived.setRequirements(null); 1969 if (base.hasRequirements() && !base.getPath().contains(".")) 1970 base.setRequirements(null); 1971 1972 if (derived.hasAlias()) { 1973 if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false)) 1974 for (StringType s : derived.getAlias()) { 1975 if (!base.hasAlias(s.getValue())) 1976 base.getAlias().add(s.copy()); 1977 } 1978 else if (trimDifferential) 1979 derived.getAlias().clear(); 1980 else 1981 for (StringType t : derived.getAlias()) 1982 t.setUserData(DERIVATION_EQUALS, true); 1983 } 1984 1985 if (derived.hasMinElement()) { 1986 if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { 1987 if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do 1988 // not apply 1989 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 1990 pn + "." + source.getPath(), 1991 "Element " + base.getPath() + ": derived min (" + Integer.toString(derived.getMin()) 1992 + ") cannot be less than base min (" + Integer.toString(base.getMin()) + ")", 1993 ValidationMessage.IssueSeverity.ERROR)); 1994 base.setMinElement(derived.getMinElement().copy()); 1995 } else if (trimDifferential) 1996 derived.setMinElement(null); 1997 else 1998 derived.getMinElement().setUserData(DERIVATION_EQUALS, true); 1999 } 2000 2001 if (derived.hasMaxElement()) { 2002 if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { 2003 if (isLargerMax(derived.getMax(), base.getMax())) 2004 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2005 pn + "." + source.getPath(), 2006 "Element " + base.getPath() + ": derived max (" + derived.getMax() 2007 + ") cannot be greater than base max (" + base.getMax() + ")", 2008 ValidationMessage.IssueSeverity.ERROR)); 2009 base.setMaxElement(derived.getMaxElement().copy()); 2010 } else if (trimDifferential) 2011 derived.setMaxElement(null); 2012 else 2013 derived.getMaxElement().setUserData(DERIVATION_EQUALS, true); 2014 } 2015 2016 if (derived.hasFixed()) { 2017 if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) { 2018 base.setFixed(derived.getFixed().copy()); 2019 } else if (trimDifferential) 2020 derived.setFixed(null); 2021 else 2022 derived.getFixed().setUserData(DERIVATION_EQUALS, true); 2023 } 2024 2025 if (derived.hasPattern()) { 2026 if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) { 2027 base.setPattern(derived.getPattern().copy()); 2028 } else if (trimDifferential) 2029 derived.setPattern(null); 2030 else 2031 derived.getPattern().setUserData(DERIVATION_EQUALS, true); 2032 } 2033 2034 for (ElementDefinitionExampleComponent ex : derived.getExample()) { 2035 boolean found = false; 2036 for (ElementDefinitionExampleComponent exS : base.getExample()) 2037 if (Base.compareDeep(ex, exS, false)) 2038 found = true; 2039 if (!found) 2040 base.addExample(ex.copy()); 2041 else if (trimDifferential) 2042 derived.getExample().remove(ex); 2043 else 2044 ex.setUserData(DERIVATION_EQUALS, true); 2045 } 2046 2047 if (derived.hasMaxLengthElement()) { 2048 if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false)) 2049 base.setMaxLengthElement(derived.getMaxLengthElement().copy()); 2050 else if (trimDifferential) 2051 derived.setMaxLengthElement(null); 2052 else 2053 derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true); 2054 } 2055 2056 if (derived.hasMaxValue()) { 2057 if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false)) 2058 base.setMaxValue(derived.getMaxValue().copy()); 2059 else if (trimDifferential) 2060 derived.setMaxValue(null); 2061 else 2062 derived.getMaxValue().setUserData(DERIVATION_EQUALS, true); 2063 } 2064 2065 if (derived.hasMinValue()) { 2066 if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false)) 2067 base.setMinValue(derived.getMinValue().copy()); 2068 else if (trimDifferential) 2069 derived.setMinValue(null); 2070 else 2071 derived.getMinValue().setUserData(DERIVATION_EQUALS, true); 2072 } 2073 2074 // todo: what to do about conditions? 2075 // condition : id 0..* 2076 2077 if (derived.hasMustSupportElement()) { 2078 if (!(base.hasMustSupportElement() 2079 && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))) 2080 base.setMustSupportElement(derived.getMustSupportElement().copy()); 2081 else if (trimDifferential) 2082 derived.setMustSupportElement(null); 2083 else 2084 derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true); 2085 } 2086 2087 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 2088 // but extensions can change isModifier 2089 if (isExtension) { 2090 if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() 2091 && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) 2092 base.setIsModifierElement(derived.getIsModifierElement().copy()); 2093 else if (trimDifferential) 2094 derived.setIsModifierElement(null); 2095 else if (derived.hasIsModifierElement()) 2096 derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true); 2097 if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() 2098 && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) 2099 base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy()); 2100 else if (trimDifferential) 2101 derived.setIsModifierReasonElement(null); 2102 else if (derived.hasIsModifierReasonElement()) 2103 derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true); 2104 } 2105 2106 if (derived.hasBinding()) { 2107 if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 2108 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED 2109 && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 2110 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2111 pn + "." + derived.getPath(), 2112 "illegal attempt to change the binding on " + derived.getPath() + " from " 2113 + base.getBinding().getStrength().toCode() + " to " + derived.getBinding().getStrength().toCode(), 2114 ValidationMessage.IssueSeverity.ERROR)); 2115// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 2116 else if (base.hasBinding() && derived.hasBinding() 2117 && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() 2118 && derived.getBinding().hasValueSet()) { 2119 ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet()); 2120 ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet()); 2121 if (baseVs == null) { 2122 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2123 pn + "." + base.getPath(), "Binding " + base.getBinding().getValueSet() + " could not be located", 2124 ValidationMessage.IssueSeverity.WARNING)); 2125 } else if (contextVs == null) { 2126 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2127 pn + "." + derived.getPath(), 2128 "Binding " + derived.getBinding().getValueSet() + " could not be located", 2129 ValidationMessage.IssueSeverity.WARNING)); 2130 } else { 2131 ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false); 2132 ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false); 2133 if (expBase.getValueset() == null) 2134 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2135 pn + "." + base.getPath(), "Binding " + base.getBinding().getValueSet() + " could not be expanded", 2136 ValidationMessage.IssueSeverity.WARNING)); 2137 else if (expDerived.getValueset() == null) 2138 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2139 pn + "." + derived.getPath(), 2140 "Binding " + derived.getBinding().getValueSet() + " could not be expanded", 2141 ValidationMessage.IssueSeverity.WARNING)); 2142 else if (!isSubset(expBase.getValueset(), expDerived.getValueset())) 2143 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2144 pn + "." + derived.getPath(), "Binding " + derived.getBinding().getValueSet() 2145 + " is not a subset of binding " + base.getBinding().getValueSet(), 2146 ValidationMessage.IssueSeverity.ERROR)); 2147 2148 } 2149 } 2150 base.setBinding(derived.getBinding().copy()); 2151 } else if (trimDifferential) 2152 derived.setBinding(null); 2153 else 2154 derived.getBinding().setUserData(DERIVATION_EQUALS, true); 2155 } // else if (base.hasBinding() && doesn't have bindable type ) 2156 // base 2157 2158 if (derived.hasIsSummaryElement()) { 2159 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) { 2160 if (base.hasIsSummary()) 2161 throw new Error("Error in profile " + pn + " at " + derived.getPath() + ": Base isSummary = " 2162 + base.getIsSummaryElement().asStringValue() + ", derived isSummary = " 2163 + derived.getIsSummaryElement().asStringValue()); 2164 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 2165 } else if (trimDifferential) 2166 derived.setIsSummaryElement(null); 2167 else 2168 derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true); 2169 } 2170 2171 if (derived.hasType()) { 2172 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 2173 if (base.hasType()) { 2174 for (TypeRefComponent ts : derived.getType()) { 2175// if (!ts.hasCode()) { // ommitted in the differential; copy it over.... 2176// if (base.getType().size() > 1) 2177// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": constrained type code must be present if there are multiple types ("+base.typeSummary()+")"); 2178// if (base.getType().get(0).getCode() != null) 2179// ts.setCode(base.getType().get(0).getCode()); 2180// } 2181 boolean ok = false; 2182 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2183 String t = ts.getWorkingCode(); 2184 for (TypeRefComponent td : base.getType()) { 2185 ; 2186 String tt = td.getWorkingCode(); 2187 b.append(tt); 2188 if (td.hasCode() 2189 && (tt.equals(t) || "Extension".equals(tt) || (t.equals("uri") && tt.equals("string")) || // work 2190 // around 2191 // for old 2192 // badly 2193 // generated 2194 // SDs 2195 "Element".equals(tt) || "*".equals(tt) 2196 || (("Resource".equals(tt) || ("DomainResource".equals(tt)) && pkp.isResource(t))))) 2197 ok = true; 2198 } 2199 if (!ok) 2200 throw new DefinitionException("StructureDefinition " + pn + " at " + derived.getPath() 2201 + ": illegal constrained type " + t + " from " + b.toString() + " in " + srcSD.getUrl()); 2202 } 2203 } 2204 base.getType().clear(); 2205 for (TypeRefComponent t : derived.getType()) { 2206 TypeRefComponent tt = t.copy(); 2207// tt.setUserData(DERIVATION_EQUALS, true); 2208 base.getType().add(tt); 2209 } 2210 } else if (trimDifferential) 2211 derived.getType().clear(); 2212 else 2213 for (TypeRefComponent t : derived.getType()) 2214 t.setUserData(DERIVATION_EQUALS, true); 2215 } 2216 2217 if (derived.hasMapping()) { 2218 // todo: mappings are not cumulative - one replaces another 2219 if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) { 2220 for (ElementDefinitionMappingComponent s : derived.getMapping()) { 2221 boolean found = false; 2222 for (ElementDefinitionMappingComponent d : base.getMapping()) { 2223 found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap())); 2224 } 2225 if (!found) 2226 base.getMapping().add(s); 2227 } 2228 } else if (trimDifferential) 2229 derived.getMapping().clear(); 2230 else 2231 for (ElementDefinitionMappingComponent t : derived.getMapping()) 2232 t.setUserData(DERIVATION_EQUALS, true); 2233 } 2234 2235 // todo: constraints are cumulative. there is no replacing 2236 for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 2237 s.setUserData(IS_DERIVED, true); 2238 if (!s.hasSource()) 2239 s.setSource(base.getId()); 2240 } 2241 if (derived.hasConstraint()) { 2242 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 2243 ElementDefinitionConstraintComponent inv = s.copy(); 2244 base.getConstraint().add(inv); 2245 } 2246 } 2247 2248 // now, check that we still have a bindable type; if not, delete the binding - 2249 // see task 8477 2250 if (dest.hasBinding() && !hasBindableType(dest)) 2251 dest.setBinding(null); 2252 2253 // finally, we copy any extensions from source to dest 2254 for (Extension ex : derived.getExtension()) { 2255 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ex.getUrl()); 2256 if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) 2257 ToolingExtensions.removeExtension(dest, ex.getUrl()); 2258 dest.addExtension(ex.copy()); 2259 } 2260 } 2261 } 2262 2263 private boolean hasBindableType(ElementDefinition ed) { 2264 for (TypeRefComponent tr : ed.getType()) { 2265 if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code")) 2266 return true; 2267 } 2268 return false; 2269 } 2270 2271 private boolean isLargerMax(String derived, String base) { 2272 if ("*".equals(base)) 2273 return false; 2274 if ("*".equals(derived)) 2275 return true; 2276 return Integer.parseInt(derived) > Integer.parseInt(base); 2277 } 2278 2279 private boolean isSubset(ValueSet expBase, ValueSet expDerived) { 2280 return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion()); 2281 } 2282 2283 private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, 2284 ValueSetExpansionComponent expansion) { 2285 for (ValueSetExpansionContainsComponent cc : contains) { 2286 if (!inExpansion(cc, expansion.getContains())) 2287 return false; 2288 if (!codesInExpansion(cc.getContains(), expansion)) 2289 return false; 2290 } 2291 return true; 2292 } 2293 2294 private boolean inExpansion(ValueSetExpansionContainsComponent cc, 2295 List<ValueSetExpansionContainsComponent> contains) { 2296 for (ValueSetExpansionContainsComponent cc1 : contains) { 2297 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) 2298 return true; 2299 if (inExpansion(cc, cc1.getContains())) 2300 return true; 2301 } 2302 return false; 2303 } 2304 2305 public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { 2306 for (ElementDefinition edb : base.getSnapshot().getElement()) { 2307 if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { 2308 ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); 2309 if (edm == null) { 2310 ElementDefinition edd = derived.getDifferential().addElement(); 2311 edd.setPath(edb.getPath()); 2312 edd.setMax("0"); 2313 } else if (edb.hasSlicing()) { 2314 closeChildren(base, edb, derived, edm); 2315 } 2316 } 2317 } 2318 sortDifferential(base, derived, derived.getName(), new ArrayList<String>()); 2319 } 2320 2321 private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, 2322 ElementDefinition edm) { 2323 String path = edb.getPath() + "."; 2324 int baseStart = base.getSnapshot().getElement().indexOf(edb); 2325 int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart + 1); 2326 int diffStart = derived.getDifferential().getElement().indexOf(edm); 2327 int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart + 1); 2328 2329 for (int cBase = baseStart; cBase < baseEnd; cBase++) { 2330 ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); 2331 if (isImmediateChild(edBase, edb)) { 2332 ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, 2333 diffEnd); 2334 if (edMatch == null) { 2335 ElementDefinition edd = derived.getDifferential().addElement(); 2336 edd.setPath(edBase.getPath()); 2337 edd.setMax("0"); 2338 } else { 2339 closeChildren(base, edBase, derived, edMatch); 2340 } 2341 } 2342 } 2343 } 2344 2345 private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) { 2346 String path = ed.getPath() + "."; 2347 while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) 2348 cursor++; 2349 return cursor; 2350 } 2351 2352 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) { 2353 for (ElementDefinition t : list) 2354 if (t.getPath().equals(ed.getPath())) 2355 return t; 2356 return null; 2357 } 2358 2359 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) { 2360 for (int i = start; i < end; i++) { 2361 ElementDefinition t = list.get(i); 2362 if (t.getPath().equals(ed.getPath())) 2363 return t; 2364 } 2365 return null; 2366 } 2367 2368 private boolean isImmediateChild(ElementDefinition ed) { 2369 String p = ed.getPath(); 2370 if (!p.contains(".")) 2371 return false; 2372 p = p.substring(p.indexOf(".") + 1); 2373 return !p.contains("."); 2374 } 2375 2376 private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { 2377 String p = candidate.getPath(); 2378 if (!p.contains(".")) 2379 return false; 2380 if (!p.startsWith(base.getPath() + ".")) 2381 return false; 2382 p = p.substring(base.getPath().length() + 1); 2383 return !p.contains("."); 2384 } 2385 2386 public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, 2387 boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) 2388 throws IOException, FHIRException { 2389 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics, true); 2390 TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false, TableGenerationMode.XML); 2391 2392 boolean deep = false; 2393 String m = ""; 2394 boolean vdeep = false; 2395 if (ed.getSnapshot().getElementFirstRep().getIsModifier()) 2396 m = "modifier_"; 2397 for (ElementDefinition eld : ed.getSnapshot().getElement()) { 2398 deep = deep || eld.getPath().contains("Extension.extension."); 2399 vdeep = vdeep || eld.getPath().contains("Extension.extension.extension."); 2400 } 2401 Row r = gen.new Row(); 2402 model.getRows().add(r); 2403 String en; 2404 if (!full) 2405 en = ed.getName(); 2406 else if (ed.getSnapshot().getElement().get(0).getIsModifier()) 2407 en = "modifierExtension"; 2408 else 2409 en = "extension"; 2410 2411 r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile + "-definitions.html#extension." + ed.getName(), 2412 en, null, null)); 2413 r.getCells().add(gen.new Cell()); 2414 r.getCells().add(gen.new Cell(null, null, 2415 describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null)); 2416 2417 ElementDefinition ved = null; 2418 if (full || vdeep) { 2419 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 2420 2421 r.setIcon(deep ? "icon_" + m + "extension_complex.png" : "icon_extension_simple.png", 2422 deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX 2423 : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2424 List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), 2425 ed.getSnapshot().getElement().get(0)); 2426 for (ElementDefinition child : children) 2427 if (!child.getPath().endsWith(".id")) 2428 genElement(defFile == null ? "" : defFile + "-definitions.html#extension.", gen, r.getSubRows(), child, 2429 ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath, imagePath, true, false, false, 2430 false, null); 2431 } else if (deep) { 2432 List<ElementDefinition> children = new ArrayList<ElementDefinition>(); 2433 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 2434 if (ted.getPath().equals("Extension.extension")) 2435 children.add(ted); 2436 } 2437 2438 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 2439 r.setIcon("icon_" + m + "extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2440 2441 for (ElementDefinition c : children) { 2442 ved = getValueFor(ed, c); 2443 ElementDefinition ued = getUrlFor(ed, c); 2444 if (ved != null && ued != null) { 2445 Row r1 = gen.new Row(); 2446 r.getSubRows().add(r1); 2447 r1.getCells() 2448 .add(gen.new Cell(null, defFile == null ? "" : defFile + "-definitions.html#extension." + ed.getName(), 2449 ((UriType) ued.getFixed()).getValue(), null, null)); 2450 r1.getCells().add(gen.new Cell()); 2451 r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null)); 2452 genTypes(gen, r1, ved, defFile, ed, corePath, imagePath); 2453 Cell cell = gen.new Cell(); 2454 cell.addMarkdown(c.getDefinition()); 2455 r1.getCells().add(cell); 2456 r1.setIcon("icon_" + m + "extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2457 } 2458 } 2459 } else { 2460 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 2461 if (ted.getPath().startsWith("Extension.value")) 2462 ved = ted; 2463 } 2464 2465 genTypes(gen, r, ved, defFile, ed, corePath, imagePath); 2466 2467 r.setIcon("icon_" + m + "extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2468 } 2469 Cell c = gen.new Cell("", "", "URL = " + ed.getUrl(), null, null); 2470 Piece cc = gen.new Piece(null, ed.getName() + ": ", null); 2471 c.addPiece(gen.new Piece("br")).addPiece(cc); 2472 c.addMarkdown(ed.getDescription()); 2473 2474 if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) { 2475 c.addPiece(gen.new Piece("br")); 2476 BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath()); 2477 c.getPieces().add(checkForNoChange(ved.getBinding(), 2478 gen.new Piece(null, translate("sd.table", "Binding") + ": ", null).addStyle("font-weight:bold"))); 2479 c.getPieces() 2480 .add(checkForNoChange(ved.getBinding(), 2481 gen.new Piece( 2482 br.url == null ? null 2483 : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url, 2484 br.display, null))); 2485 if (ved.getBinding().hasStrength()) { 2486 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null))); 2487 c.getPieces() 2488 .add(checkForNoChange(ved.getBinding(), 2489 gen.new Piece(corePath + "terminologies.html#" + ved.getBinding().getStrength().toCode(), 2490 egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition()))); 2491 c.getPieces().add(gen.new Piece(null, ")", null)); 2492 } 2493 } 2494 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null)); 2495 r.getCells().add(c); 2496 2497 try { 2498 return gen.generate(model, corePath, 0, outputTracker); 2499 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2500 throw new FHIRException(e.getMessage(), e); 2501 } 2502 } 2503 2504 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 2505 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 2506 while (i < ed.getSnapshot().getElement().size() 2507 && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".")) { 2508 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath() + ".url")) 2509 return ed.getSnapshot().getElement().get(i); 2510 i++; 2511 } 2512 return null; 2513 } 2514 2515 private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) { 2516 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 2517 while (i < ed.getSnapshot().getElement().size() 2518 && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".")) { 2519 if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".value")) 2520 return ed.getSnapshot().getElement().get(i); 2521 i++; 2522 } 2523 return null; 2524 } 2525 2526 private static final int AGG_NONE = 0; 2527 private static final int AGG_IND = 1; 2528 private static final int AGG_GR = 2; 2529 private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false; 2530 2531 private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, 2532 StructureDefinition profile, String corePath, String imagePath) { 2533 Cell c = gen.new Cell(); 2534 r.getCells().add(c); 2535 List<TypeRefComponent> types = e.getType(); 2536 if (!e.hasType()) { 2537 if (e.hasContentReference()) { 2538 return c; 2539 } else { 2540 ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER); 2541 if (d != null && d.hasType()) { 2542 types = new ArrayList<ElementDefinition.TypeRefComponent>(); 2543 for (TypeRefComponent tr : d.getType()) { 2544 TypeRefComponent tt = tr.copy(); 2545 tt.setUserData(DERIVATION_EQUALS, true); 2546 types.add(tt); 2547 } 2548 } else 2549 return c; 2550 } 2551 } 2552 2553 boolean first = true; 2554 2555 TypeRefComponent tl = null; 2556 for (TypeRefComponent t : types) { 2557 if (first) 2558 first = false; 2559 else 2560 c.addPiece(checkForNoChange(tl, gen.new Piece(null, ", ", null))); 2561 tl = t; 2562 if (t.hasTarget()) { 2563 c.getPieces().add(gen.new Piece(corePath + "references.html", t.getWorkingCode(), null)); 2564 c.getPieces().add(gen.new Piece(null, "(", null)); 2565 boolean tfirst = true; 2566 for (UriType u : t.getTargetProfile()) { 2567 if (tfirst) 2568 tfirst = false; 2569 else 2570 c.addPiece(gen.new Piece(null, " | ", null)); 2571 genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue()); 2572 } 2573 c.getPieces().add(gen.new Piece(null, ")", null)); 2574 if (t.getAggregation().size() > 0) { 2575 c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", " {", null)); 2576 boolean firstA = true; 2577 for (Enumeration<AggregationMode> a : t.getAggregation()) { 2578 if (firstA = true) 2579 firstA = false; 2580 else 2581 c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", ", ", null)); 2582 c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", 2583 codeForAggregation(a.getValue()), hintForAggregation(a.getValue()))); 2584 } 2585 c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", "}", null)); 2586 } 2587 } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a 2588 // profiled 2589 // type 2590 String ref; 2591 ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue()); 2592 if (ref != null) { 2593 String[] parts = ref.split("\\|"); 2594 if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) { 2595// c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd 2596 c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode()))); 2597 } else { 2598// c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode()))); 2599 c.addPiece(checkForNoChange(t, 2600 gen.new Piece( 2601 (t.getProfile().get(0).getValue().startsWith(corePath + "StructureDefinition") ? corePath : "") 2602 + parts[0], 2603 parts[1], t.getWorkingCode()))); 2604 } 2605 } else 2606 c.addPiece(checkForNoChange(t, 2607 gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath) ? corePath : "") + ref, 2608 t.getWorkingCode(), null))); 2609 } else { 2610 String tc = t.getWorkingCode(); 2611 if (pkp != null && pkp.hasLinkFor(tc)) { 2612 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null))); 2613 } else 2614 c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null))); 2615 } 2616 } 2617 return c; 2618 } 2619 2620 public void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, 2621 TypeRefComponent t, String u) { 2622 if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2623 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u); 2624 if (sd != null) { 2625 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 2626 c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null))); 2627 } else { 2628 String rn = u.substring(40); 2629 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null))); 2630 } 2631 } else if (Utilities.isAbsoluteUrl(u)) { 2632 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u); 2633 if (sd != null) { 2634 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 2635 String ref = pkp.getLinkForProfile(null, sd.getUrl()); 2636 if (ref.contains("|")) 2637 ref = ref.substring(0, ref.indexOf("|")); 2638 c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null))); 2639 } else 2640 c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null))); 2641 } else if (t.hasTargetProfile() && u.startsWith("#")) 2642 c.addPiece(checkForNoChange(t, 2643 gen.new Piece(corePath + profileBaseFileName + "." + u.substring(1).toLowerCase() + ".html", u, null))); 2644 } 2645 2646 private boolean isProfiledType(List<CanonicalType> theProfile) { 2647 for (CanonicalType next : theProfile) { 2648 if (StringUtils.defaultString(next.getValueAsString()).contains(":")) { 2649 return true; 2650 } 2651 } 2652 return false; 2653 } 2654 2655 private String codeForAggregation(AggregationMode a) { 2656 switch (a) { 2657 case BUNDLED: 2658 return "b"; 2659 case CONTAINED: 2660 return "c"; 2661 case REFERENCED: 2662 return "r"; 2663 default: 2664 return "?"; 2665 } 2666 } 2667 2668 private String hintForAggregation(AggregationMode a) { 2669 if (a != null) 2670 return a.getDefinition(); 2671 else 2672 return null; 2673 } 2674 2675 private String checkPrepend(String corePath, String path) { 2676 if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:"))) 2677 return corePath + path; 2678 else 2679 return path; 2680 } 2681 2682 private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) { 2683 for (ElementDefinition ed : elements) 2684 if (ed.hasSliceName() && ("#" + ed.getSliceName()).equals(contentReference)) 2685 return ed; 2686 return null; 2687 } 2688 2689 private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) { 2690 for (ElementDefinition ed : elements) 2691 if (ed.hasId() && ("#" + ed.getId()).equals(contentReference)) 2692 return ed; 2693 return null; 2694 } 2695 2696 public static String describeExtensionContext(StructureDefinition ext) { 2697 StringBuilder b = new StringBuilder(); 2698 b.append("Use on "); 2699 for (int i = 0; i < ext.getContext().size(); i++) { 2700 StructureDefinitionContextComponent ec = ext.getContext().get(i); 2701 if (i > 0) 2702 b.append(i < ext.getContext().size() - 1 ? ", " : " or "); 2703 b.append(ec.getType().getDisplay()); 2704 b.append(" "); 2705 b.append(ec.getExpression()); 2706 } 2707 if (ext.hasContextInvariant()) { 2708 b.append( 2709 ", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = "); 2710 boolean first = true; 2711 for (StringType s : ext.getContextInvariant()) { 2712 if (first) 2713 first = false; 2714 else 2715 b.append(", "); 2716 b.append("<code>" + s.getValue() + "</code>"); 2717 } 2718 } 2719 return b.toString(); 2720 } 2721 2722 private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) { 2723 IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 2724 StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 2725 if (min.isEmpty() && fallback != null) 2726 min = fallback.getMinElement(); 2727 if (max.isEmpty() && fallback != null) 2728 max = fallback.getMaxElement(); 2729 2730 tracker.used = !max.isEmpty() && !max.getValue().equals("0"); 2731 2732 if (min.isEmpty() && max.isEmpty()) 2733 return null; 2734 else 2735 return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue()); 2736 } 2737 2738 private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, 2739 UnusedTracker tracker, ElementDefinition fallback) { 2740 IntegerType min = !hasDef ? new IntegerType() 2741 : definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 2742 StringType max = !hasDef ? new StringType() 2743 : definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 2744 if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 2745 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 2746 if (base.hasMinElement()) { 2747 min = base.getMinElement().copy(); 2748 min.setUserData(DERIVATION_EQUALS, true); 2749 } 2750 } 2751 if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 2752 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 2753 if (base.hasMaxElement()) { 2754 max = base.getMaxElement().copy(); 2755 max.setUserData(DERIVATION_EQUALS, true); 2756 } 2757 } 2758 if (min.isEmpty() && fallback != null) 2759 min = fallback.getMinElement(); 2760 if (max.isEmpty() && fallback != null) 2761 max = fallback.getMaxElement(); 2762 2763 if (!max.isEmpty()) 2764 tracker.used = !max.getValue().equals("0"); 2765 2766 Cell cell = gen.new Cell(null, null, null, null, null); 2767 row.getCells().add(cell); 2768 if (!min.isEmpty() || !max.isEmpty()) { 2769 cell.addPiece( 2770 checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null))); 2771 cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null))); 2772 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null))); 2773 } 2774 } 2775 2776 private Piece checkForNoChange(Element source, Piece piece) { 2777 if (source.hasUserData(DERIVATION_EQUALS)) { 2778 piece.addStyle("opacity: 0.4"); 2779 } 2780 return piece; 2781 } 2782 2783 private Piece checkForNoChange(Element src1, Element src2, Piece piece) { 2784 if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) { 2785 piece.addStyle("opacity: 0.5"); 2786 } 2787 return piece; 2788 } 2789 2790 public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, 2791 boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, 2792 boolean logicalModel, boolean allInvariants, Set<String> outputTracker) throws IOException, FHIRException { 2793 assert (diff != snapshot);// check it's ok to get rid of one of these 2794 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics, true); 2795 TableModel model = gen.initNormalTable(corePath, false, true, profile.getId() + (diff ? "d" : "s"), false, 2796 TableGenerationMode.XML); 2797 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 2798 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2799 profiles.add(profile); 2800 if (list.isEmpty()) { 2801 ElementDefinition root = new ElementDefinition().setPath(profile.getType()); 2802 root.setId(profile.getType()); 2803 list.add(root); 2804 } 2805 genElement(defFile == null ? null : defFile + "#", gen, model.getRows(), list.get(0), list, profiles, diff, 2806 profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, 2807 profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants, null); 2808 try { 2809 return gen.generate(model, imagePath, 0, outputTracker); 2810 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2811 throw new FHIRException("Error generating table for profile " + profile.getUrl() + ": " + e.getMessage(), e); 2812 } 2813 } 2814 2815 public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, 2816 String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) 2817 throws IOException, FHIRException { 2818 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics, true); 2819 TableModel model = gen.initGridTable(corePath, profile.getId()); 2820 List<ElementDefinition> list = profile.getSnapshot().getElement(); 2821 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2822 profiles.add(profile); 2823 genGridElement(defFile == null ? null : defFile + "#", gen, model.getRows(), list.get(0), list, profiles, true, 2824 profileBaseFileName, null, corePath, imagePath, true, 2825 profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list)); 2826 try { 2827 return gen.generate(model, imagePath, 1, outputTracker); 2828 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2829 throw new FHIRException(e.getMessage(), e); 2830 } 2831 } 2832 2833 private boolean usesMustSupport(List<ElementDefinition> list) { 2834 for (ElementDefinition ed : list) 2835 if (ed.hasMustSupport() && ed.getMustSupport()) 2836 return true; 2837 return false; 2838 } 2839 2840 private Row genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, 2841 List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, 2842 Boolean extensions, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, 2843 boolean isConstraintMode, boolean allInvariants, Row slicingRow) throws IOException, FHIRException { 2844 Row originalRow = slicingRow; 2845 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size() - 1); 2846 String s = tail(element.getPath()); 2847 if (element.hasSliceName()) 2848 s = s + ":" + element.getSliceName(); 2849 Row typesRow = null; 2850 2851 List<ElementDefinition> children = getChildren(all, element); 2852 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 2853// if (!snapshot && isExtension && extensions != null && extensions != isExtension) 2854// return; 2855 2856 if (!onlyInformationIsMapping(all, element)) { 2857 Row row = gen.new Row(); 2858 row.setAnchor(element.getPath()); 2859 row.setColor(getRowColor(element, isConstraintMode)); 2860 if (element.hasSlicing()) 2861 row.setLineColor(1); 2862 else if (element.hasSliceName()) 2863 row.setLineColor(2); 2864 else 2865 row.setLineColor(0); 2866 boolean hasDef = element != null; 2867 boolean ext = false; 2868 if (tail(element.getPath()).equals("extension")) { 2869 if (element.hasType() && element.getType().get(0).hasProfile() 2870 && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 2871 row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2872 else 2873 row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2874 ext = true; 2875 } else if (tail(element.getPath()).equals("modifierExtension")) { 2876 if (element.hasType() && element.getType().get(0).hasProfile() 2877 && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 2878 row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2879 else 2880 row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2881 } else if (!hasDef || element.getType().size() == 0) 2882 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 2883 else if (hasDef && element.getType().size() > 1) { 2884 if (allAreReference(element.getType())) 2885 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2886 else { 2887 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 2888 typesRow = row; 2889 } 2890 } else if (hasDef && element.getType().get(0).getWorkingCode() != null 2891 && element.getType().get(0).getWorkingCode().startsWith("@")) 2892 row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE); 2893 else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode())) 2894 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 2895 else if (hasDef && element.getType().get(0).hasTarget()) 2896 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2897 else if (hasDef && isDataType(element.getType().get(0).getWorkingCode())) 2898 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2899 else 2900 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 2901 String ref = defPath == null ? null : defPath + element.getId(); 2902 UnusedTracker used = new UnusedTracker(); 2903 used.used = true; 2904 if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR)) 2905 s = "@" + s; 2906 Cell left = gen.new Cell(null, ref, s, 2907 (element.hasSliceName() ? translate("sd.table", "Slice") + " " + element.getSliceName() : "") 2908 + (hasDef && element.hasSliceName() ? ": " : "") + (!hasDef ? null : gt(element.getDefinitionElement())), 2909 null); 2910 row.getCells().add(left); 2911 Cell gc = gen.new Cell(); 2912 row.getCells().add(gc); 2913 if (element != null && element.getIsModifier()) 2914 checkForNoChange(element.getIsModifierElement(), gc 2915 .addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false)); 2916 if (element != null && element.getMustSupport()) 2917 checkForNoChange(element.getMustSupportElement(), gc 2918 .addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false)); 2919 if (element != null && element.getIsSummary()) 2920 checkForNoChange(element.getIsSummaryElement(), gc.addStyledText( 2921 translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false)); 2922 if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty())) 2923 gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null, 2924 null, false); 2925 2926 ExtensionContext extDefn = null; 2927 if (ext) { 2928 if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) { 2929 String eurl = element.getType().get(0).getProfile().get(0).getValue(); 2930 extDefn = locateExtension(StructureDefinition.class, eurl); 2931 if (extDefn == null) { 2932 genCardinality(gen, element, row, hasDef, used, null); 2933 row.getCells().add(gen.new Cell(null, null, "?? " + element.getType().get(0).getProfile(), null, null)); 2934 generateDescription(gen, row, element, null, used.used, profile.getUrl(), eurl, profile, corePath, 2935 imagePath, root, logicalModel, allInvariants, snapshot); 2936 } else { 2937 String name = urlFragmentOrTail(eurl); 2938 left.getPieces().get(0).setText(name); 2939 // left.getPieces().get(0).setReference((String) 2940 // extDefn.getExtensionStructure().getTag("filename")); 2941 left.getPieces().get(0).setHint(translate("sd.table", "Extension URL") + " = " + extDefn.getUrl()); 2942 genCardinality(gen, element, row, hasDef, used, extDefn.getElement()); 2943 ElementDefinition valueDefn = extDefn.getExtensionValueDefinition(); 2944 if (valueDefn != null && !"0".equals(valueDefn.getMax())) 2945 genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath); 2946 else // if it's complex, we just call it nothing 2947 // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), 2948 // profileBaseFileName, profile); 2949 row.getCells().add(gen.new Cell(null, null, "(" + translate("sd.table", "Complex") + ")", null, null)); 2950 generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, 2951 corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot); 2952 } 2953 } else { 2954 genCardinality(gen, element, row, hasDef, used, null); 2955 if ("0".equals(element.getMax())) 2956 row.getCells().add(gen.new Cell()); 2957 else 2958 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2959 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, 2960 logicalModel, allInvariants, snapshot); 2961 } 2962 } else { 2963 genCardinality(gen, element, row, hasDef, used, null); 2964 if (element.hasSlicing()) 2965 row.getCells().add(gen.new Cell(null, corePath + "profiling.html#slicing", "(Slice Definition)", null, null)); 2966 else if (hasDef && !"0".equals(element.getMax()) && typesRow == null) 2967 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2968 else 2969 row.getCells().add(gen.new Cell()); 2970 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, 2971 logicalModel, allInvariants, snapshot); 2972 } 2973 if (element.hasSlicing()) { 2974 if (standardExtensionSlicing(element)) { 2975 used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing 2976 // ... element.hasType() && element.getType().get(0).hasProfile(); 2977 showMissing = false; // ? 2978 } else { 2979 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 2980 slicingRow = row; 2981 for (Cell cell : row.getCells()) 2982 for (Piece p : cell.getPieces()) { 2983 p.addStyle("font-style: italic"); 2984 } 2985 } 2986 } else if (element.hasSliceName()) { 2987 row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM); 2988 } 2989 if (used.used || showMissing) 2990 rows.add(row); 2991 if (!used.used && !element.hasSlicing()) { 2992 for (Cell cell : row.getCells()) 2993 for (Piece p : cell.getPieces()) { 2994 p.setStyle("text-decoration:line-through"); 2995 p.setReference(null); 2996 } 2997 } else { 2998 if (slicingRow != originalRow && !children.isEmpty()) { 2999 // we've entered a slice; we're going to create a holder row for the slice 3000 // children 3001 Row hrow = gen.new Row(); 3002 hrow.setAnchor(element.getPath()); 3003 hrow.setColor(getRowColor(element, isConstraintMode)); 3004 hrow.setLineColor(1); 3005 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 3006 hrow.getCells().add(gen.new Cell(null, null, "(All Slices)", "", null)); 3007 hrow.getCells().add(gen.new Cell()); 3008 hrow.getCells().add(gen.new Cell()); 3009 hrow.getCells().add(gen.new Cell()); 3010 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null)); 3011 row.getSubRows().add(hrow); 3012 row = hrow; 3013 } 3014 if (typesRow != null && !children.isEmpty()) { 3015 // we've entered a typing slice; we're going to create a holder row for the all 3016 // types children 3017 Row hrow = gen.new Row(); 3018 hrow.setAnchor(element.getPath()); 3019 hrow.setColor(getRowColor(element, isConstraintMode)); 3020 hrow.setLineColor(1); 3021 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 3022 hrow.getCells().add(gen.new Cell(null, null, "(All Types)", "", null)); 3023 hrow.getCells().add(gen.new Cell()); 3024 hrow.getCells().add(gen.new Cell()); 3025 hrow.getCells().add(gen.new Cell()); 3026 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null)); 3027 row.getSubRows().add(hrow); 3028 row = hrow; 3029 } 3030 3031 Row currRow = row; 3032 for (ElementDefinition child : children) { 3033 if (!child.hasSliceName()) 3034 currRow = row; 3035 if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) 3036 && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) 3037 currRow = genElement(defPath, gen, currRow.getSubRows(), child, all, profiles, showMissing, 3038 profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, 3039 allInvariants, currRow); 3040 } 3041// if (!snapshot && (extensions == null || !extensions)) 3042// for (ElementDefinition child : children) 3043// if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension")) 3044// genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants); 3045 } 3046 if (typesRow != null) { 3047 makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName); 3048 } 3049 } 3050 return slicingRow; 3051 } 3052 3053 private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, 3054 String corePath, String profileBaseFileName) { 3055 // create a child for each choice 3056 for (TypeRefComponent tr : element.getType()) { 3057 Row choicerow = gen.new Row(); 3058 String t = tr.getWorkingCode(); 3059 if (isReference(t)) { 3060 choicerow.getCells() 3061 .add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null)); 3062 choicerow.getCells().add(gen.new Cell()); 3063 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 3064 choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 3065 Cell c = gen.new Cell(); 3066 choicerow.getCells().add(c); 3067 if (ADD_REFERENCE_TO_TABLE) { 3068 if (tr.getWorkingCode().equals("canonical")) 3069 c.getPieces().add(gen.new Piece(corePath + "datatypes.html#canonical", "canonical", null)); 3070 else 3071 c.getPieces().add(gen.new Piece(corePath + "references.html#Reference", "Reference", null)); 3072 c.getPieces().add(gen.new Piece(null, "(", null)); 3073 } 3074 boolean first = true; 3075 for (CanonicalType rt : tr.getTargetProfile()) { 3076 if (!first) 3077 c.getPieces().add(gen.new Piece(null, " | ", null)); 3078 genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue()); 3079 first = false; 3080 } 3081 if (ADD_REFERENCE_TO_TABLE) 3082 c.getPieces().add(gen.new Piece(null, ")", null)); 3083 3084 } else { 3085 StructureDefinition sd = context.fetchTypeDefinition(t); 3086 if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 3087 choicerow.getCells().add(gen.new Cell(null, null, 3088 tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 3089 choicerow.getCells().add(gen.new Cell()); 3090 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 3091 choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 3092 choicerow.getCells().add(gen.new Cell(null, corePath + "datatypes.html#" + t, t, null, null)); 3093 // } else if (definitions.getConstraints().contthnsKey(t)) { 3094 // ProfiledType pt = definitions.getConstraints().get(t); 3095 // choicerow.getCells().add(gen.new Cell(null, null, e.getName().replace("[x]", 3096 // Utilities.capitalize(pt.getBaseType())), 3097 // definitions.getTypes().containsKey(t) ? 3098 // definitions.getTypes().get(t).getDefinition() : null, null)); 3099 // choicerow.getCells().add(gen.new Cell()); 3100 // choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 3101 // choicerow.setIcon("icon_datatype.gif", 3102 // HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 3103 // choicerow.getCells().add(gen.new Cell(null, 3104 // definitions.getSrcFile(t)+".html#"+t.replace("*", "open"), t, null, null)); 3105 } else { 3106 choicerow.getCells().add(gen.new Cell(null, null, 3107 tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 3108 choicerow.getCells().add(gen.new Cell()); 3109 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 3110 choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 3111 choicerow.getCells().add(gen.new Cell(null, pkp.getLinkFor(corePath, t), t, null, null)); 3112 } 3113 } 3114 choicerow.getCells().add(gen.new Cell()); 3115 subRows.add(choicerow); 3116 } 3117 } 3118 3119 private boolean isReference(String t) { 3120 return t.equals("Reference") || t.equals("canonical"); 3121 } 3122 3123 private void genGridElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, 3124 List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, 3125 Boolean extensions, String corePath, String imagePath, boolean root, boolean isConstraintMode) 3126 throws IOException, FHIRException { 3127 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size() - 1); 3128 String s = tail(element.getPath()); 3129 List<ElementDefinition> children = getChildren(all, element); 3130 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 3131 3132 if (!onlyInformationIsMapping(all, element)) { 3133 Row row = gen.new Row(); 3134 row.setAnchor(element.getPath()); 3135 row.setColor(getRowColor(element, isConstraintMode)); 3136 if (element.hasSlicing()) 3137 row.setLineColor(1); 3138 else if (element.hasSliceName()) 3139 row.setLineColor(2); 3140 else 3141 row.setLineColor(0); 3142 boolean hasDef = element != null; 3143 String ref = defPath == null ? null : defPath + element.getId(); 3144 UnusedTracker used = new UnusedTracker(); 3145 used.used = true; 3146 Cell left = gen.new Cell(); 3147 if (element.getType().size() == 1 && element.getType().get(0).isPrimitive()) 3148 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())) 3149 .addStyle("font-weight:bold")); 3150 else 3151 left.getPieces() 3152 .add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement()))); 3153 if (element.hasSliceName()) { 3154 left.getPieces().add(gen.new Piece("br")); 3155 String indent = StringUtils.repeat('\u00A0', 1 + 2 * (element.getPath().split("\\.").length)); 3156 left.getPieces().add(gen.new Piece(null, indent + "(" + element.getSliceName() + ")", null)); 3157 } 3158 row.getCells().add(left); 3159 3160 ExtensionContext extDefn = null; 3161 genCardinality(gen, element, row, hasDef, used, null); 3162 if (hasDef && !"0".equals(element.getMax())) 3163 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 3164 else 3165 row.getCells().add(gen.new Cell()); 3166 generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null); 3167 /* 3168 * if (element.hasSlicing()) { if (standardExtensionSlicing(element)) { 3169 * used.used = element.hasType() && element.getType().get(0).hasProfile(); 3170 * showMissing = false; } else { row.setIcon("icon_slice.png", 3171 * HierarchicalTableGenerator.TEXT_ICON_SLICE); 3172 * row.getCells().get(2).getPieces().clear(); for (Cell cell : row.getCells()) 3173 * for (Piece p : cell.getPieces()) { p.addStyle("font-style: italic"); } } } 3174 */ 3175 rows.add(row); 3176 for (ElementDefinition child : children) 3177 if (child.getMustSupport()) 3178 genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, 3179 isExtension, corePath, imagePath, false, isConstraintMode); 3180 } 3181 } 3182 3183 private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value) { 3184 if (value.contains("#")) { 3185 StructureDefinition ext = context.fetchResource(StructureDefinition.class, 3186 value.substring(0, value.indexOf("#"))); 3187 if (ext == null) 3188 return null; 3189 String tail = value.substring(value.indexOf("#") + 1); 3190 ElementDefinition ed = null; 3191 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 3192 if (tail.equals(ted.getSliceName())) { 3193 ed = ted; 3194 return new ExtensionContext(ext, ed); 3195 } 3196 } 3197 return null; 3198 } else { 3199 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 3200 if (ext == null) 3201 return null; 3202 else 3203 return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0)); 3204 } 3205 } 3206 3207 private boolean extensionIsComplex(String value) { 3208 if (value.contains("#")) { 3209 StructureDefinition ext = context.fetchResource(StructureDefinition.class, 3210 value.substring(0, value.indexOf("#"))); 3211 if (ext == null) 3212 return false; 3213 String tail = value.substring(value.indexOf("#") + 1); 3214 ElementDefinition ed = null; 3215 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 3216 if (tail.equals(ted.getSliceName())) { 3217 ed = ted; 3218 break; 3219 } 3220 } 3221 if (ed == null) 3222 return false; 3223 int i = ext.getSnapshot().getElement().indexOf(ed); 3224 int j = i + 1; 3225 while (j < ext.getSnapshot().getElement().size() 3226 && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath())) 3227 j++; 3228 return j - i > 5; 3229 } else { 3230 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 3231 return ext != null && ext.getSnapshot().getElement().size() > 5; 3232 } 3233 } 3234 3235 private String getRowColor(ElementDefinition element, boolean isConstraintMode) { 3236 switch (element.getUserInt(UD_ERROR_STATUS)) { 3237 case STATUS_HINT: 3238 return ROW_COLOR_HINT; 3239 case STATUS_WARNING: 3240 return ROW_COLOR_WARNING; 3241 case STATUS_ERROR: 3242 return ROW_COLOR_ERROR; 3243 case STATUS_FATAL: 3244 return ROW_COLOR_FATAL; 3245 } 3246 if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains(".")) 3247 return null; // ROW_COLOR_NOT_MUST_SUPPORT; 3248 else 3249 return null; 3250 } 3251 3252 private String urlFragmentOrTail(String path) { 3253 if (path.contains("#")) 3254 return path.substring(path.lastIndexOf('#') + 1); 3255 if (path.contains("/")) 3256 return path.substring(path.lastIndexOf('/') + 1); 3257 else 3258 return path; 3259 3260 } 3261 3262 private boolean standardExtensionSlicing(ElementDefinition element) { 3263 String t = tail(element.getPath()); 3264 return (t.equals("extension") || t.equals("modifierExtension")) 3265 && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 3266 && element.getSlicing().getDiscriminator().get(0).getPath().equals("url") 3267 && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE); 3268 } 3269 3270 private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, 3271 ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, 3272 String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, boolean snapshot) 3273 throws IOException, FHIRException { 3274 return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, 3275 logicalModel, allInvariants, null, snapshot); 3276 } 3277 3278 private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, 3279 ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, 3280 String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, 3281 ElementDefinition valueDefn, boolean snapshot) throws IOException, FHIRException { 3282 Cell c = gen.new Cell(); 3283 row.getCells().add(c); 3284 3285 if (used) { 3286 if (logicalModel && ToolingExtensions.hasExtension(profile, 3287 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) { 3288 if (root) { 3289 c.getPieces().add( 3290 gen.new Piece(null, translate("sd.table", "XML Namespace") + ": ", null).addStyle("font-weight:bold")); 3291 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, 3292 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 3293 } else if (!root 3294 && ToolingExtensions.hasExtension(definition, 3295 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") 3296 && !ToolingExtensions 3297 .readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") 3298 .equals(ToolingExtensions.readStringExtension(profile, 3299 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) { 3300 c.getPieces().add( 3301 gen.new Piece(null, translate("sd.table", "XML Namespace") + ": ", null).addStyle("font-weight:bold")); 3302 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, 3303 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 3304 } 3305 } 3306 3307 if (definition.hasContentReference()) { 3308 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference()); 3309 if (ed == null) 3310 c.getPieces().add(gen.new Piece(null, 3311 translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null)); 3312 else 3313 c.getPieces().add(gen.new Piece("#" + ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null)); 3314 } 3315 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 3316 c.getPieces().add(checkForNoChange(definition.getFixed(), 3317 gen.new Piece(null, "\"" + buildJson(definition.getFixed()) + "\"", null).addStyle("color: darkgreen"))); 3318 } else { 3319 if (definition != null && definition.hasShort()) { 3320 if (!c.getPieces().isEmpty()) 3321 c.addPiece(gen.new Piece("br")); 3322 c.addPiece(checkForNoChange(definition.getShortElement(), 3323 gen.new Piece(null, gt(definition.getShortElement()), null))); 3324 } else if (fallback != null && fallback.hasShort()) { 3325 if (!c.getPieces().isEmpty()) 3326 c.addPiece(gen.new Piece("br")); 3327 c.addPiece( 3328 checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null))); 3329 } 3330 if (url != null) { 3331 if (!c.getPieces().isEmpty()) 3332 c.addPiece(gen.new Piece("br")); 3333 String fullUrl = url.startsWith("#") ? baseURL + url : url; 3334 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 3335 String ref = null; 3336 String ref2 = null; 3337 String fixedUrl = null; 3338 if (ed != null) { 3339 String p = ed.getUserString("path"); 3340 if (p != null) { 3341 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 3342 } 3343 fixedUrl = getFixedUrl(ed); 3344 if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension? 3345 if (fixedUrl.equals(url)) 3346 fixedUrl = null; 3347 else { 3348 StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl); 3349 if (ed2 != null) { 3350 String p2 = ed2.getUserString("path"); 3351 if (p2 != null) { 3352 ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2); 3353 } 3354 } 3355 } 3356 } 3357 } 3358 if (fixedUrl == null) { 3359 c.getPieces() 3360 .add(gen.new Piece(null, translate("sd.table", "URL") + ": ", null).addStyle("font-weight:bold")); 3361 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3362 } else { 3363 // reference to a profile take on the extension show the base URL 3364 c.getPieces() 3365 .add(gen.new Piece(null, translate("sd.table", "URL") + ": ", null).addStyle("font-weight:bold")); 3366 c.getPieces().add(gen.new Piece(ref2, fixedUrl, null)); 3367 c.getPieces().add( 3368 gen.new Piece(null, translate("sd.table", " profiled by ") + " ", null).addStyle("font-weight:bold")); 3369 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3370 3371 } 3372 } 3373 3374 if (definition.hasSlicing()) { 3375 if (!c.getPieces().isEmpty()) 3376 c.addPiece(gen.new Piece("br")); 3377 c.getPieces() 3378 .add(gen.new Piece(null, translate("sd.table", "Slice") + ": ", null).addStyle("font-weight:bold")); 3379 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 3380 } 3381 if (definition != null) { 3382 ElementDefinitionBindingComponent binding = null; 3383 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 3384 binding = valueDefn.getBinding(); 3385 else if (definition.hasBinding()) 3386 binding = definition.getBinding(); 3387 if (binding != null && !binding.isEmpty()) { 3388 if (!c.getPieces().isEmpty()) 3389 c.addPiece(gen.new Piece("br")); 3390 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 3391 c.getPieces().add(checkForNoChange(binding, 3392 gen.new Piece(null, translate("sd.table", "Binding") + ": ", null).addStyle("font-weight:bold"))); 3393 c.getPieces() 3394 .add(checkForNoChange(binding, 3395 gen.new Piece( 3396 br.url == null ? null 3397 : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url, 3398 br.display, null))); 3399 if (binding.hasStrength()) { 3400 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 3401 c.getPieces() 3402 .add(checkForNoChange(binding, 3403 gen.new Piece(corePath + "terminologies.html#" + binding.getStrength().toCode(), 3404 egt(binding.getStrengthElement()), binding.getStrength().getDefinition()))); 3405 3406 c.getPieces().add(gen.new Piece(null, ")", null)); 3407 } 3408 if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) { 3409 br = pkp.resolveBinding(profile, 3410 ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), 3411 definition.getPath()); 3412 c.addPiece(gen.new Piece("br")); 3413 c.getPieces() 3414 .add(checkForNoChange(binding, 3415 gen.new Piece(corePath + "extension-elementdefinition-maxvalueset.html", 3416 translate("sd.table", "Max Binding") + ": ", "Max Value Set Extension") 3417 .addStyle("font-weight:bold"))); 3418 c.getPieces() 3419 .add(checkForNoChange(binding, 3420 gen.new Piece( 3421 br.url == null ? null 3422 : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url, 3423 br.display, null))); 3424 } 3425 if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) { 3426 br = pkp.resolveBinding(profile, 3427 ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), 3428 definition.getPath()); 3429 c.addPiece(gen.new Piece("br")); 3430 c.getPieces() 3431 .add(checkForNoChange(binding, 3432 gen.new Piece(corePath + "extension-elementdefinition-minvalueset.html", 3433 translate("sd.table", "Min Binding") + ": ", "Min Value Set Extension") 3434 .addStyle("font-weight:bold"))); 3435 c.getPieces() 3436 .add(checkForNoChange(binding, 3437 gen.new Piece( 3438 br.url == null ? null 3439 : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url, 3440 br.display, null))); 3441 } 3442 } 3443 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 3444 if (!inv.hasSource() || allInvariants) { 3445 if (!c.getPieces().isEmpty()) 3446 c.addPiece(gen.new Piece("br")); 3447 c.getPieces().add( 3448 checkForNoChange(inv, gen.new Piece(null, inv.getKey() + ": ", null).addStyle("font-weight:bold"))); 3449 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null))); 3450 } 3451 } 3452 if ((definition.hasBase() && definition.getBase().getMax().equals("*")) 3453 || (definition.hasMax() && definition.getMax().equals("*"))) { 3454 if (c.getPieces().size() > 0) 3455 c.addPiece(gen.new Piece("br")); 3456 if (definition.hasOrderMeaning()) { 3457 c.getPieces() 3458 .add(gen.new Piece(null, "This repeating element order: " + definition.getOrderMeaning(), null)); 3459 } else { 3460 // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, 3461 // "This repeating element has no defined order", null)); 3462 } 3463 } 3464 3465 if (definition.hasFixed()) { 3466 if (!c.getPieces().isEmpty()) 3467 c.addPiece(gen.new Piece("br")); 3468 c.getPieces().add(checkForNoChange(definition.getFixed(), 3469 gen.new Piece(null, translate("sd.table", "Fixed Value") + ": ", null).addStyle("font-weight:bold"))); 3470 if (!useTableForFixedValues || definition.getFixed().isPrimitive()) { 3471 String s = buildJson(definition.getFixed()); 3472 String link = null; 3473 if (Utilities.isAbsoluteUrl(s)) 3474 link = pkp.getLinkForUrl(corePath, s); 3475 c.getPieces().add( 3476 checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 3477 } else { 3478 c.getPieces().add(checkForNoChange(definition.getFixed(), 3479 gen.new Piece(null, "As shown", null).addStyle("color: darkgreen"))); 3480 genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath); 3481 } 3482 if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) { 3483 Piece p = describeCoded(gen, definition.getFixed()); 3484 if (p != null) 3485 c.getPieces().add(p); 3486 } 3487 } else if (definition.hasPattern()) { 3488 if (!c.getPieces().isEmpty()) 3489 c.addPiece(gen.new Piece("br")); 3490 c.getPieces() 3491 .add(checkForNoChange(definition.getPattern(), 3492 gen.new Piece(null, translate("sd.table", "Required Pattern") + ": ", null) 3493 .addStyle("font-weight:bold"))); 3494 if (!useTableForFixedValues || definition.getPattern().isPrimitive()) 3495 c.getPieces().add(checkForNoChange(definition.getPattern(), 3496 gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 3497 else { 3498 c.getPieces().add(checkForNoChange(definition.getPattern(), 3499 gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen"))); 3500 genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath); 3501 } 3502 } else if (definition.hasExample()) { 3503 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 3504 if (!c.getPieces().isEmpty()) 3505 c.addPiece(gen.new Piece("br")); 3506 c.getPieces() 3507 .add(checkForNoChange(ex, 3508 gen.new Piece(null, translate("sd.table", "Example") 3509 + ("".equals("General") ? "" : " " + ex.getLabel() + "'") + ": ", null) 3510 .addStyle("font-weight:bold"))); 3511 c.getPieces().add(checkForNoChange(ex, 3512 gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 3513 } 3514 } 3515 if (definition.hasMaxLength() && definition.getMaxLength() != 0) { 3516 if (!c.getPieces().isEmpty()) 3517 c.addPiece(gen.new Piece("br")); 3518 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), 3519 gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 3520 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), 3521 gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 3522 } 3523 if (profile != null) { 3524 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 3525 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 3526 ElementDefinitionMappingComponent map = null; 3527 for (ElementDefinitionMappingComponent m : definition.getMapping()) 3528 if (m.getIdentity().equals(md.getIdentity())) 3529 map = m; 3530 if (map != null) { 3531 for (int i = 0; i < definition.getMapping().size(); i++) { 3532 c.addPiece(gen.new Piece("br")); 3533 c.getPieces().add( 3534 gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME) 3535 + ": " + map.getMap(), null)); 3536 } 3537 } 3538 } 3539 } 3540 } 3541 } 3542 } 3543 } 3544 return c; 3545 } 3546 3547 private void genFixedValue(HierarchicalTableGenerator gen, Row erow, Type value, boolean snapshot, boolean pattern, 3548 String corePath) { 3549 String ref = pkp.getLinkFor(corePath, value.fhirType()); 3550 ref = ref.substring(0, ref.indexOf(".html")) + "-definitions.html#"; 3551 StructureDefinition sd = context.fetchTypeDefinition(value.fhirType()); 3552 3553 for (org.hl7.fhir.r4.model.Property t : value.children()) { 3554 if (t.getValues().size() > 0 || snapshot) { 3555 ElementDefinition ed = findElementDefinition(sd, t.getName()); 3556 if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) { 3557 Row row = gen.new Row(); 3558 erow.getSubRows().add(row); 3559 Cell c = gen.new Cell(); 3560 row.getCells().add(c); 3561 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref + ed.getPath() 3562 : corePath + "element-definitions.html#" + ed.getBase().getPath()), t.getName(), null)); 3563 c = gen.new Cell(); 3564 row.getCells().add(c); 3565 c.addPiece(gen.new Piece(null, null, null)); 3566 c = gen.new Cell(); 3567 row.getCells().add(c); 3568 if (!pattern) { 3569 c.addPiece(gen.new Piece(null, "0..0", null)); 3570 row.setIcon("icon_fixed.gif", "Fixed Value" /* HierarchicalTableGenerator.TEXT_ICON_FIXED */); 3571 } else if (isPrimitive(t.getTypeCode())) { 3572 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 3573 c.addPiece(gen.new Piece(null, 3574 "0.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 3575 } else if (isReference(t.getTypeCode())) { 3576 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 3577 c.addPiece(gen.new Piece(null, 3578 "0.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 3579 } else { 3580 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 3581 c.addPiece(gen.new Piece(null, 3582 "0.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 3583 } 3584 c = gen.new Cell(); 3585 row.getCells().add(c); 3586 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null)); 3587 c = gen.new Cell(); 3588 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3589 row.getCells().add(c); 3590 } else { 3591 for (Base b : t.getValues()) { 3592 Row row = gen.new Row(); 3593 erow.getSubRows().add(row); 3594 row.setIcon("icon_fixed.gif", "Fixed Value" /* HierarchicalTableGenerator.TEXT_ICON_FIXED */); 3595 3596 Cell c = gen.new Cell(); 3597 row.getCells().add(c); 3598 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref + ed.getPath() 3599 : corePath + "element-definitions.html#" + ed.getBase().getPath()), t.getName(), null)); 3600 3601 c = gen.new Cell(); 3602 row.getCells().add(c); 3603 c.addPiece(gen.new Piece(null, null, null)); 3604 3605 c = gen.new Cell(); 3606 row.getCells().add(c); 3607 if (pattern) 3608 c.addPiece(gen.new Piece(null, 3609 "1.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 3610 else 3611 c.addPiece(gen.new Piece(null, "1..1", null)); 3612 3613 c = gen.new Cell(); 3614 row.getCells().add(c); 3615 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, b.fhirType()), b.fhirType(), null)); 3616 3617 if (b.isPrimitive()) { 3618 c = gen.new Cell(); 3619 row.getCells().add(c); 3620 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3621 c.addPiece(gen.new Piece("br")); 3622 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 3623 String s = b.primitiveValue(); 3624 // ok. let's see if we can find a relevant link for this 3625 String link = null; 3626 if (Utilities.isAbsoluteUrl(s)) 3627 link = pkp.getLinkForUrl(corePath, s); 3628 c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen")); 3629 } else { 3630 c = gen.new Cell(); 3631 row.getCells().add(c); 3632 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3633 c.addPiece(gen.new Piece("br")); 3634 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 3635 c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen")); 3636 genFixedValue(gen, row, (Type) b, snapshot, pattern, corePath); 3637 } 3638 } 3639 } 3640 } 3641 } 3642 } 3643 3644 private ElementDefinition findElementDefinition(StructureDefinition sd, String name) { 3645 String path = sd.getType() + "." + name; 3646 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3647 if (ed.getPath().equals(path)) 3648 return ed; 3649 } 3650 throw new FHIRException("Unable to find element " + path); 3651 } 3652 3653 private String getFixedUrl(StructureDefinition sd) { 3654 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3655 if (ed.getPath().equals("Extension.url")) { 3656 if (ed.hasFixed() && ed.getFixed() instanceof UriType) 3657 return ed.getFixed().primitiveValue(); 3658 } 3659 } 3660 return null; 3661 } 3662 3663 private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) { 3664 if (fixed instanceof Coding) { 3665 Coding c = (Coding) fixed; 3666 ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), c.getDisplay()); 3667 if (vr.getDisplay() != null) 3668 return gen.new Piece(null, " (" + vr.getDisplay() + ")", null).addStyle("color: darkgreen"); 3669 } else if (fixed instanceof CodeableConcept) { 3670 CodeableConcept cc = (CodeableConcept) fixed; 3671 for (Coding c : cc.getCoding()) { 3672 ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), 3673 c.getDisplay()); 3674 if (vr.getDisplay() != null) 3675 return gen.new Piece(null, " (" + vr.getDisplay() + ")", null).addStyle("color: darkgreen"); 3676 } 3677 } 3678 return null; 3679 } 3680 3681 private boolean hasDescription(Type fixed) { 3682 if (fixed instanceof Coding) { 3683 return ((Coding) fixed).hasDisplay(); 3684 } else if (fixed instanceof CodeableConcept) { 3685 CodeableConcept cc = (CodeableConcept) fixed; 3686 if (cc.hasText()) 3687 return true; 3688 for (Coding c : cc.getCoding()) 3689 if (c.hasDisplay()) 3690 return true; 3691 } // (fixed instanceof CodeType) || (fixed instanceof Quantity); 3692 return false; 3693 } 3694 3695 private boolean isCoded(Type fixed) { 3696 return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) 3697 || (fixed instanceof Quantity); 3698 } 3699 3700 private Cell generateGridDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, 3701 ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, 3702 String corePath, String imagePath, boolean root, ElementDefinition valueDefn) throws IOException, FHIRException { 3703 Cell c = gen.new Cell(); 3704 row.getCells().add(c); 3705 3706 if (used) { 3707 if (definition.hasContentReference()) { 3708 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference()); 3709 if (ed == null) 3710 c.getPieces().add(gen.new Piece(null, "Unknown reference to " + definition.getContentReference(), null)); 3711 else 3712 c.getPieces().add(gen.new Piece("#" + ed.getPath(), "See " + ed.getPath(), null)); 3713 } 3714 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 3715 c.getPieces().add(checkForNoChange(definition.getFixed(), 3716 gen.new Piece(null, "\"" + buildJson(definition.getFixed()) + "\"", null).addStyle("color: darkgreen"))); 3717 } else { 3718 if (url != null) { 3719 if (!c.getPieces().isEmpty()) 3720 c.addPiece(gen.new Piece("br")); 3721 String fullUrl = url.startsWith("#") ? baseURL + url : url; 3722 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 3723 String ref = null; 3724 if (ed != null) { 3725 String p = ed.getUserString("path"); 3726 if (p != null) { 3727 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 3728 } 3729 } 3730 c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold")); 3731 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3732 } 3733 3734 if (definition.hasSlicing()) { 3735 if (!c.getPieces().isEmpty()) 3736 c.addPiece(gen.new Piece("br")); 3737 c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); 3738 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 3739 } 3740 if (definition != null) { 3741 ElementDefinitionBindingComponent binding = null; 3742 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 3743 binding = valueDefn.getBinding(); 3744 else if (definition.hasBinding()) 3745 binding = definition.getBinding(); 3746 if (binding != null && !binding.isEmpty()) { 3747 if (!c.getPieces().isEmpty()) 3748 c.addPiece(gen.new Piece("br")); 3749 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 3750 c.getPieces() 3751 .add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold"))); 3752 c.getPieces() 3753 .add(checkForNoChange(binding, 3754 gen.new Piece( 3755 br.url == null ? null 3756 : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url, 3757 br.display, null))); 3758 if (binding.hasStrength()) { 3759 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 3760 c.getPieces() 3761 .add(checkForNoChange(binding, 3762 gen.new Piece(corePath + "terminologies.html#" + binding.getStrength().toCode(), 3763 binding.getStrength().toCode(), binding.getStrength().getDefinition()))); 3764 c.getPieces().add(gen.new Piece(null, ")", null)); 3765 } 3766 } 3767 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 3768 if (!c.getPieces().isEmpty()) 3769 c.addPiece(gen.new Piece("br")); 3770 c.getPieces().add( 3771 checkForNoChange(inv, gen.new Piece(null, inv.getKey() + ": ", null).addStyle("font-weight:bold"))); 3772 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); 3773 } 3774 if (definition.hasFixed()) { 3775 if (!c.getPieces().isEmpty()) 3776 c.addPiece(gen.new Piece("br")); 3777 c.getPieces().add(checkForNoChange(definition.getFixed(), 3778 gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); 3779 String s = buildJson(definition.getFixed()); 3780 String link = null; 3781 if (Utilities.isAbsoluteUrl(s)) 3782 link = pkp.getLinkForUrl(corePath, s); 3783 c.getPieces().add( 3784 checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 3785 } else if (definition.hasPattern()) { 3786 if (!c.getPieces().isEmpty()) 3787 c.addPiece(gen.new Piece("br")); 3788 c.getPieces().add(checkForNoChange(definition.getPattern(), 3789 gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); 3790 c.getPieces().add(checkForNoChange(definition.getPattern(), 3791 gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 3792 } else if (definition.hasExample()) { 3793 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 3794 if (!c.getPieces().isEmpty()) 3795 c.addPiece(gen.new Piece("br")); 3796 c.getPieces().add(checkForNoChange(ex, 3797 gen.new Piece(null, "Example'" + ("".equals("General") ? "" : " " + ex.getLabel() + "'") + ": ", null) 3798 .addStyle("font-weight:bold"))); 3799 c.getPieces().add(checkForNoChange(ex, 3800 gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 3801 } 3802 } 3803 if (definition.hasMaxLength() && definition.getMaxLength() != 0) { 3804 if (!c.getPieces().isEmpty()) 3805 c.addPiece(gen.new Piece("br")); 3806 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), 3807 gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 3808 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), 3809 gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 3810 } 3811 if (profile != null) { 3812 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 3813 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 3814 ElementDefinitionMappingComponent map = null; 3815 for (ElementDefinitionMappingComponent m : definition.getMapping()) 3816 if (m.getIdentity().equals(md.getIdentity())) 3817 map = m; 3818 if (map != null) { 3819 for (int i = 0; i < definition.getMapping().size(); i++) { 3820 c.addPiece(gen.new Piece("br")); 3821 c.getPieces().add( 3822 gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME) 3823 + ": " + map.getMap(), null)); 3824 } 3825 } 3826 } 3827 } 3828 } 3829 if (definition.hasDefinition()) { 3830 if (!c.getPieces().isEmpty()) 3831 c.addPiece(gen.new Piece("br")); 3832 c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold")); 3833 c.addPiece(gen.new Piece("br")); 3834 c.addMarkdown(definition.getDefinition()); 3835// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 3836 } 3837 if (definition.getComment() != null) { 3838 if (!c.getPieces().isEmpty()) 3839 c.addPiece(gen.new Piece("br")); 3840 c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold")); 3841 c.addPiece(gen.new Piece("br")); 3842 c.addMarkdown(definition.getComment()); 3843// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 3844 } 3845 } 3846 } 3847 } 3848 return c; 3849 } 3850 3851 private String buildJson(Type value) throws IOException { 3852 if (value instanceof PrimitiveType) 3853 return ((PrimitiveType) value).asStringValue(); 3854 3855 IParser json = context.newJsonParser(); 3856 return json.composeString(value, null); 3857 } 3858 3859 public String describeSlice(ElementDefinitionSlicingComponent slicing) { 3860 return translate("sd.table", "%s, %s by %s", 3861 slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), 3862 describe(slicing.getRules()), commas(slicing.getDiscriminator())); 3863 } 3864 3865 private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) { 3866 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 3867 for (ElementDefinitionSlicingDiscriminatorComponent id : list) 3868 c.append(id.getType().toCode() + ":" + id.getPath()); 3869 return c.toString(); 3870 } 3871 3872 private String describe(SlicingRules rules) { 3873 if (rules == null) 3874 return translate("sd.table", "Unspecified"); 3875 switch (rules) { 3876 case CLOSED: 3877 return translate("sd.table", "Closed"); 3878 case OPEN: 3879 return translate("sd.table", "Open"); 3880 case OPENATEND: 3881 return translate("sd.table", "Open At End"); 3882 default: 3883 return "??"; 3884 } 3885 } 3886 3887 private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) { 3888 return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) && getChildren(list, e).isEmpty(); 3889 } 3890 3891 private boolean onlyInformationIsMapping(ElementDefinition d) { 3892 return !d.hasShort() && !d.hasDefinition() && !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() 3893 && !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() && !d.hasExample() && !d.hasFixed() 3894 && !d.hasMaxLengthElement() && !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() 3895 && !d.hasMustSupportElement() && !d.hasBinding(); 3896 } 3897 3898 private boolean allAreReference(List<TypeRefComponent> types) { 3899 for (TypeRefComponent t : types) { 3900 if (!t.hasTarget()) 3901 return false; 3902 } 3903 return true; 3904 } 3905 3906 private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) { 3907 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 3908 int i = all.indexOf(element) + 1; 3909 while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) { 3910 if ((all.get(i).getPath().substring(0, element.getPath().length() + 1).equals(element.getPath() + ".")) 3911 && !all.get(i).getPath().substring(element.getPath().length() + 1).contains(".")) 3912 result.add(all.get(i)); 3913 i++; 3914 } 3915 return result; 3916 } 3917 3918 private String tail(String path) { 3919 if (path.contains(".")) 3920 return path.substring(path.lastIndexOf('.') + 1); 3921 else 3922 return path; 3923 } 3924 3925 private boolean isDataType(String value) { 3926 StructureDefinition sd = context.fetchTypeDefinition(value); 3927 if (sd == null) // might be running before all SDs are available 3928 return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", 3929 "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", 3930 "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", "ContactDetail", "Contributor", 3931 "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", 3932 "UsageContext"); 3933 else 3934 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE 3935 && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 3936 } 3937 3938 private boolean isConstrainedDataType(String value) { 3939 StructureDefinition sd = context.fetchTypeDefinition(value); 3940 if (sd == null) // might be running before all SDs are available 3941 return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"); 3942 else 3943 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT; 3944 } 3945 3946 private String baseType(String value) { 3947 StructureDefinition sd = context.fetchTypeDefinition(value); 3948 if (sd != null) // might be running before all SDs are available 3949 return sd.getType(); 3950 if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity")) 3951 return "Quantity"; 3952 throw new Error("Internal error - type not known " + value); 3953 } 3954 3955 public boolean isPrimitive(String value) { 3956 StructureDefinition sd = context.fetchTypeDefinition(value); 3957 if (sd == null) // might be running before all SDs are available 3958 return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", 3959 "decimal", "id", "instant", "integer", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", 3960 "uri", "url", "uuid"); 3961 else 3962 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 3963 } 3964 3965// private static String listStructures(StructureDefinition p) { 3966// StringBuilder b = new StringBuilder(); 3967// boolean first = true; 3968// for (ProfileStructureComponent s : p.getStructure()) { 3969// if (first) 3970// first = false; 3971// else 3972// b.append(", "); 3973// if (pkp != null && pkp.hasLinkFor(s.getType())) 3974// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 3975// else 3976// b.append(s.getType()); 3977// } 3978// return b.toString(); 3979// } 3980 3981 public StructureDefinition getProfile(StructureDefinition source, String url) { 3982 StructureDefinition profile = null; 3983 String code = null; 3984 if (url.startsWith("#")) { 3985 profile = source; 3986 code = url.substring(1); 3987 } else if (context != null) { 3988 String[] parts = url.split("\\#"); 3989 profile = context.fetchResource(StructureDefinition.class, parts[0]); 3990 code = parts.length == 1 ? null : parts[1]; 3991 } 3992 if (profile == null) 3993 return null; 3994 if (code == null) 3995 return profile; 3996 for (Resource r : profile.getContained()) { 3997 if (r instanceof StructureDefinition && r.getId().equals(code)) 3998 return (StructureDefinition) r; 3999 } 4000 return null; 4001 } 4002 4003 public static class ElementDefinitionHolder { 4004 private String name; 4005 private ElementDefinition self; 4006 private int baseIndex = 0; 4007 private List<ElementDefinitionHolder> children; 4008 private boolean placeHolder = false; 4009 4010 public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) { 4011 super(); 4012 this.self = self; 4013 this.name = self.getPath(); 4014 this.placeHolder = isPlaceholder; 4015 children = new ArrayList<ElementDefinitionHolder>(); 4016 } 4017 4018 public ElementDefinitionHolder(ElementDefinition self) { 4019 this(self, false); 4020 } 4021 4022 public ElementDefinition getSelf() { 4023 return self; 4024 } 4025 4026 public List<ElementDefinitionHolder> getChildren() { 4027 return children; 4028 } 4029 4030 public int getBaseIndex() { 4031 return baseIndex; 4032 } 4033 4034 public void setBaseIndex(int baseIndex) { 4035 this.baseIndex = baseIndex; 4036 } 4037 4038 public boolean isPlaceHolder() { 4039 return this.placeHolder; 4040 } 4041 4042 @Override 4043 public String toString() { 4044 if (self.hasSliceName()) 4045 return self.getPath() + "(" + self.getSliceName() + ")"; 4046 else 4047 return self.getPath(); 4048 } 4049 } 4050 4051 public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 4052 4053 private boolean inExtension; 4054 private List<ElementDefinition> snapshot; 4055 private int prefixLength; 4056 private String base; 4057 private String name; 4058 private Set<String> errors = new HashSet<String>(); 4059 4060 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, 4061 int prefixLength, String name) { 4062 this.inExtension = inExtension; 4063 this.snapshot = snapshot; 4064 this.prefixLength = prefixLength; 4065 this.base = base; 4066 this.name = name; 4067 } 4068 4069 @Override 4070 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 4071 if (o1.getBaseIndex() == 0) 4072 o1.setBaseIndex(find(o1.getSelf().getPath())); 4073 if (o2.getBaseIndex() == 0) 4074 o2.setBaseIndex(find(o2.getSelf().getPath())); 4075 return o1.getBaseIndex() - o2.getBaseIndex(); 4076 } 4077 4078 private int find(String path) { 4079 String op = path; 4080 int lc = 0; 4081 String actual = base + path.substring(prefixLength); 4082 for (int i = 0; i < snapshot.size(); i++) { 4083 String p = snapshot.get(i).getPath(); 4084 if (p.equals(actual)) { 4085 return i; 4086 } 4087 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length() - 3)) && !(actual.endsWith("[x]")) 4088 && !actual.substring(p.length() - 3).contains(".")) { 4089 return i; 4090 } 4091 if (path.startsWith(p + ".") && snapshot.get(i).hasContentReference()) { 4092 String ref = snapshot.get(i).getContentReference(); 4093 if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1, 2))) { 4094 actual = base + (ref.substring(1) + "." + path.substring(p.length() + 1)).substring(prefixLength); 4095 path = actual; 4096 } else { 4097 // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter 4098 // instead of #Parameters.parameter, so we have to handle that 4099 actual = base 4100 + (path.substring(0, path.indexOf(".") + 1) + ref.substring(1) + "." + path.substring(p.length() + 1)) 4101 .substring(prefixLength); 4102 path = actual; 4103 } 4104 4105 i = 0; 4106 lc++; 4107 if (lc > MAX_RECURSION_LIMIT) 4108 throw new Error("Internal recursion detection: find() loop path recursion > " + MAX_RECURSION_LIMIT 4109 + " - check paths are valid (for path " + path + "/" + op + ")"); 4110 } 4111 } 4112 if (prefixLength == 0) 4113 errors.add("Differential contains path " + path + " which is not found in the base"); 4114 else 4115 errors.add( 4116 "Differential contains path " + path + " which is actually " + actual + ", which is not found in the base"); 4117 return 0; 4118 } 4119 4120 public void checkForErrors(List<String> errorList) { 4121 if (errors.size() > 0) { 4122// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 4123// for (String s : errors) 4124// b.append("StructureDefinition "+name+": "+s); 4125// throw new DefinitionException(b.toString()); 4126 for (String s : errors) 4127 if (s.startsWith("!")) 4128 errorList.add("!StructureDefinition " + name + ": " + s.substring(1)); 4129 else 4130 errorList.add("StructureDefinition " + name + ": " + s); 4131 } 4132 } 4133 } 4134 4135 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) 4136 throws FHIRException { 4137 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 4138 int lastCount = diffList.size(); 4139 // first, we move the differential elements into a tree 4140 if (diffList.isEmpty()) 4141 return; 4142 4143 ElementDefinitionHolder edh = null; 4144 int i = 0; 4145 if (diffList.get(0).getPath().contains(".")) { 4146 String newPath = diffList.get(0).getPath().split("\\.")[0]; 4147 ElementDefinition e = new ElementDefinition(new StringType(newPath)); 4148 edh = new ElementDefinitionHolder(e, true); 4149 } else { 4150 edh = new ElementDefinitionHolder(diffList.get(0)); 4151 i = 1; 4152 } 4153 4154 boolean hasSlicing = false; 4155 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 4156 for (ElementDefinition elt : diffList) { 4157 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 4158 hasSlicing = true; 4159 break; 4160 } 4161 paths.add(elt.getPath()); 4162 } 4163 if (!hasSlicing) { 4164 // if Differential does not have slicing then safe to pre-sort the list 4165 // so elements and subcomponents are together 4166 Collections.sort(diffList, new ElementNameCompare()); 4167 } 4168 4169 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 4170 4171 // now, we sort the siblings throughout the tree 4172 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name); 4173 sortElements(edh, cmp, errors); 4174 4175 // now, we serialise them back to a list 4176 diffList.clear(); 4177 writeElements(edh, diffList); 4178 4179 if (lastCount != diffList.size()) 4180 errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal"); 4181 } 4182 4183 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 4184 String path = edh.getSelf().getPath(); 4185 final String prefix = path + "."; 4186 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 4187 if (list.get(i).getPath().substring(prefix.length() + 1).contains(".")) { 4188 String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0]; 4189 ElementDefinition e = new ElementDefinition(new StringType(newPath)); 4190 ElementDefinitionHolder child = new ElementDefinitionHolder(e, true); 4191 edh.getChildren().add(child); 4192 i = processElementsIntoTree(child, i, list); 4193 4194 } else { 4195 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 4196 edh.getChildren().add(child); 4197 i = processElementsIntoTree(child, i + 1, list); 4198 } 4199 } 4200 return i; 4201 } 4202 4203 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) 4204 throws FHIRException { 4205 if (edh.getChildren().size() == 1) 4206 // special case - sort needsto allocate base numbers, but there'll be no sort if 4207 // there's only 1 child. So in that case, we just go ahead and allocated base 4208 // number directly 4209 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath()); 4210 else 4211 Collections.sort(edh.getChildren(), cmp); 4212 cmp.checkForErrors(errors); 4213 4214 for (ElementDefinitionHolder child : edh.getChildren()) { 4215 if (child.getChildren().size() > 0) { 4216 ElementDefinitionComparer ccmp = getComparer(cmp, child); 4217 if (ccmp != null) 4218 sortElements(child, ccmp, errors); 4219 } 4220 } 4221 } 4222 4223 public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) 4224 throws FHIRException, Error { 4225 // what we have to check for here is running off the base profile into a data 4226 // type profile 4227 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 4228 ElementDefinitionComparer ccmp; 4229 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) 4230 || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) { 4231 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name); 4232 } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 4233 && child.getSelf().getType().get(0).hasProfile()) { 4234 StructureDefinition profile = context.fetchResource(StructureDefinition.class, 4235 child.getSelf().getType().get(0).getProfile().get(0).getValue()); 4236 if (profile == null) 4237 ccmp = null; // this might happen before everything is loaded. And we don't so much care 4238 // about sot order in this case 4239 else 4240 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), 4241 ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 4242 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) { 4243 StructureDefinition profile = context.fetchResource(StructureDefinition.class, 4244 sdNs(ed.getType().get(0).getWorkingCode())); 4245 if (profile == null) 4246 throw new FHIRException( 4247 "Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 4248 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), 4249 ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 4250 } else if (child.getSelf().getType().size() == 1) { 4251 StructureDefinition profile = context.fetchResource(StructureDefinition.class, 4252 sdNs(child.getSelf().getType().get(0).getWorkingCode())); 4253 if (profile == null) 4254 throw new FHIRException( 4255 "Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 4256 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), 4257 child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 4258 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 4259 String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2"); 4260 String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2"); 4261 String p = childLastNode.substring(edLastNode.length() - 3); 4262 if (isPrimitive(Utilities.uncapitalize(p))) 4263 p = Utilities.uncapitalize(p); 4264 StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p)); 4265 if (sd == null) 4266 throw new Error("Unable to find profile '" + p + "' at " + ed.getId()); 4267 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), 4268 cmp.name); 4269 } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) { 4270 for (TypeRefComponent t : child.getSelf().getType()) { 4271 if (!t.getWorkingCode().equals("Reference")) { 4272 throw new Error( 4273 "Can't have children on an element with a polymorphic type - you must slice and constrain the types first (sortElements: " 4274 + ed.getPath() + ":" + typeCode(ed.getType()) + ")"); 4275 } 4276 } 4277 StructureDefinition profile = context.fetchResource(StructureDefinition.class, 4278 sdNs(ed.getType().get(0).getWorkingCode())); 4279 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), 4280 ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 4281 } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) { 4282 for (TypeRefComponent t : ed.getType()) { 4283 if (!t.getWorkingCode().equals("Reference")) { 4284 throw new Error("Not handled yet (sortElements: " + ed.getPath() + ":" + typeCode(ed.getType()) + ")"); 4285 } 4286 } 4287 StructureDefinition profile = context.fetchResource(StructureDefinition.class, 4288 sdNs(ed.getType().get(0).getWorkingCode())); 4289 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), 4290 ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 4291 } else { 4292 // this is allowed if we only profile the extensions 4293 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element")); 4294 if (profile == null) 4295 throw new FHIRException( 4296 "Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 4297 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", 4298 child.getSelf().getPath().length(), cmp.name); 4299// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 4300 } 4301 return ccmp; 4302 } 4303 4304 private static String sdNs(String type) { 4305 return sdNs(type, null); 4306 } 4307 4308 public static String sdNs(String type, String overrideVersionNs) { 4309 if (Utilities.isAbsoluteUrl(type)) 4310 return type; 4311 else if (overrideVersionNs != null) 4312 return Utilities.pathURL(overrideVersionNs, type); 4313 else 4314 return "http://hl7.org/fhir/StructureDefinition/" + type; 4315 } 4316 4317 private boolean isAbstract(String code) { 4318 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") 4319 || code.equals("DomainResource"); 4320 } 4321 4322 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 4323 if (!edh.isPlaceHolder()) 4324 list.add(edh.getSelf()); 4325 for (ElementDefinitionHolder child : edh.getChildren()) { 4326 writeElements(child, list); 4327 } 4328 } 4329 4330 /** 4331 * First compare element by path then by name if same 4332 */ 4333 private static class ElementNameCompare implements Comparator<ElementDefinition> { 4334 4335 @Override 4336 public int compare(ElementDefinition o1, ElementDefinition o2) { 4337 String path1 = normalizePath(o1); 4338 String path2 = normalizePath(o2); 4339 int cmp = path1.compareTo(path2); 4340 if (cmp == 0) { 4341 String name1 = o1.hasSliceName() ? o1.getSliceName() : ""; 4342 String name2 = o2.hasSliceName() ? o2.getSliceName() : ""; 4343 cmp = name1.compareTo(name2); 4344 } 4345 return cmp; 4346 } 4347 4348 private static String normalizePath(ElementDefinition e) { 4349 if (!e.hasPath()) 4350 return ""; 4351 String path = e.getPath(); 4352 // if sorting element names make sure onset[x] appears before onsetAge, 4353 // onsetDate, etc. 4354 // so strip off the [x] suffix when comparing the path names. 4355 if (path.endsWith("[x]")) { 4356 path = path.substring(0, path.length() - 3); 4357 } 4358 return path; 4359 } 4360 4361 } 4362 4363 // generate schematrons for the rules in a structure definition 4364 public void generateSchematrons(OutputStream dest, StructureDefinition structure) 4365 throws IOException, DefinitionException { 4366 if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT) 4367 throw new DefinitionException("not the right kind of structure to generate schematrons for"); 4368 if (!structure.hasSnapshot()) 4369 throw new DefinitionException("needs a snapshot"); 4370 4371 StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition()); 4372 4373 if (base != null) { 4374 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 4375 4376 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 4377 generateForChildren(sch, "f:" + ed.getPath(), ed, structure, base); 4378 sch.dump(); 4379 } 4380 } 4381 4382 // generate a CSV representation of the structure definition 4383 public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) 4384 throws IOException, DefinitionException, Exception { 4385 if (!structure.hasSnapshot()) 4386 throw new DefinitionException("needs a snapshot"); 4387 4388 CSVWriter csv = new CSVWriter(dest, structure, asXml); 4389 4390 for (ElementDefinition child : structure.getSnapshot().getElement()) { 4391 csv.processElement(child); 4392 } 4393 csv.dump(); 4394 } 4395 4396 // generate an Excel representation of the structure definition 4397 public void generateXlsx(OutputStream dest, StructureDefinition structure, boolean asXml, 4398 boolean hideMustSupportFalse) throws IOException, DefinitionException, Exception { 4399 if (!structure.hasSnapshot()) 4400 throw new DefinitionException("needs a snapshot"); 4401 4402 XLSXWriter xlsx = new XLSXWriter(dest, structure, asXml, hideMustSupportFalse); 4403 4404 for (ElementDefinition child : structure.getSnapshot().getElement()) { 4405 xlsx.processElement(child); 4406 } 4407 xlsx.dump(); 4408 xlsx.close(); 4409 } 4410 4411 private class Slicer extends ElementDefinitionSlicingComponent { 4412 String criteria = ""; 4413 String name = ""; 4414 boolean check; 4415 4416 public Slicer(boolean cantCheck) { 4417 super(); 4418 this.check = cantCheck; 4419 } 4420 } 4421 4422 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, 4423 StructureDefinition structure) { 4424 // given a child in a structure, it's sliced. figure out the slicing xpath 4425 if (child.getPath().endsWith(".extension")) { 4426 ElementDefinition ued = getUrlFor(structure, child); 4427 if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile()))) 4428 return new Slicer(false); 4429 else { 4430 Slicer s = new Slicer(true); 4431 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() 4432 : ((UriType) ued.getFixed()).asStringValue(); 4433 s.name = " with URL = '" + url + "'"; 4434 s.criteria = "[@url = '" + url + "']"; 4435 return s; 4436 } 4437 } else 4438 return new Slicer(false); 4439 } 4440 4441 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, 4442 StructureDefinition structure, StructureDefinition base) throws IOException { 4443 // generateForChild(txt, structure, child); 4444 List<ElementDefinition> children = getChildList(structure, ed); 4445 String sliceName = null; 4446 ElementDefinitionSlicingComponent slicing = null; 4447 for (ElementDefinition child : children) { 4448 String name = tail(child.getPath()); 4449 if (child.hasSlicing()) { 4450 sliceName = name; 4451 slicing = child.getSlicing(); 4452 } else if (!name.equals(sliceName)) 4453 slicing = null; 4454 4455 ElementDefinition based = getByPath(base, child.getPath()); 4456 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 4457 boolean doMax = child.hasMax() && !child.getMax().equals("*") 4458 && (based == null || (!child.getMax().equals(based.getMax()))); 4459 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 4460 if (slicer.check) { 4461 if (doMin || doMax) { 4462 Section s = sch.section(xpath); 4463 Rule r = s.rule(xpath); 4464 if (doMin) 4465 r.assrt("count(f:" + name + slicer.criteria + ") >= " + Integer.toString(child.getMin()), 4466 name + slicer.name + ": minimum cardinality of '" + name + "' is " + Integer.toString(child.getMin())); 4467 if (doMax) 4468 r.assrt("count(f:" + name + slicer.criteria + ") <= " + child.getMax(), 4469 name + slicer.name + ": maximum cardinality of '" + name + "' is " + child.getMax()); 4470 } 4471 } 4472 } 4473 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 4474 if (inv.hasXpath()) { 4475 Section s = sch.section(ed.getPath()); 4476 Rule r = s.rule(xpath); 4477 r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId() + ": " : "") + inv.getHuman() 4478 + (inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 4479 } 4480 } 4481 for (ElementDefinition child : children) { 4482 String name = tail(child.getPath()); 4483 generateForChildren(sch, xpath + "/f:" + name, child, structure, base); 4484 } 4485 } 4486 4487 private ElementDefinition getByPath(StructureDefinition base, String path) { 4488 for (ElementDefinition ed : base.getSnapshot().getElement()) { 4489 if (ed.getPath().equals(path)) 4490 return ed; 4491 if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length() - 3 4492 && ed.getPath().substring(0, ed.getPath().length() - 3).equals(path.substring(0, ed.getPath().length() - 3))) 4493 return ed; 4494 } 4495 return null; 4496 } 4497 4498 public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException { 4499 if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { 4500 if (!sd.hasDifferential()) 4501 sd.setDifferential(new StructureDefinitionDifferentialComponent()); 4502 generateIds(sd.getDifferential().getElement(), sd.getUrl()); 4503 } 4504 if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { 4505 if (!sd.hasSnapshot()) 4506 sd.setSnapshot(new StructureDefinitionSnapshotComponent()); 4507 generateIds(sd.getSnapshot().getElement(), sd.getUrl()); 4508 } 4509 } 4510 4511 private boolean hasMissingIds(List<ElementDefinition> list) { 4512 for (ElementDefinition ed : list) { 4513 if (!ed.hasId()) 4514 return true; 4515 } 4516 return false; 4517 } 4518 4519 public class SliceList { 4520 4521 private Map<String, String> slices = new HashMap<>(); 4522 4523 public void seeElement(ElementDefinition ed) { 4524 Iterator<Map.Entry<String, String>> iter = slices.entrySet().iterator(); 4525 while (iter.hasNext()) { 4526 Map.Entry<String, String> entry = iter.next(); 4527 if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath())) 4528 iter.remove(); 4529 } 4530 4531 if (ed.hasSliceName()) 4532 slices.put(ed.getPath(), ed.getSliceName()); 4533 } 4534 4535 public String[] analyse(List<String> paths) { 4536 String s = paths.get(0); 4537 String[] res = new String[paths.size()]; 4538 res[0] = null; 4539 for (int i = 1; i < paths.size(); i++) { 4540 s = s + "." + paths.get(i); 4541 if (slices.containsKey(s)) 4542 res[i] = slices.get(s); 4543 else 4544 res[i] = null; 4545 } 4546 return res; 4547 } 4548 4549 } 4550 4551 private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException { 4552 if (list.isEmpty()) 4553 return; 4554 4555 Map<String, String> idMap = new HashMap<String, String>(); 4556 Map<String, String> idList = new HashMap<String, String>(); 4557 4558 SliceList sliceInfo = new SliceList(); 4559 // first pass, update the element ids 4560 for (ElementDefinition ed : list) { 4561 List<String> paths = new ArrayList<String>(); 4562 if (!ed.hasPath()) 4563 throw new DefinitionException( 4564 "No path on element Definition " + Integer.toString(list.indexOf(ed)) + " in " + name); 4565 sliceInfo.seeElement(ed); 4566 String[] pl = ed.getPath().split("\\."); 4567 for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus 4568 paths.add(pl[i]); 4569 String slices[] = sliceInfo.analyse(paths); 4570 4571 StringBuilder b = new StringBuilder(); 4572 b.append(paths.get(0)); 4573 for (int i = 1; i < paths.size(); i++) { 4574 b.append("."); 4575 String s = paths.get(i); 4576 String p = slices[i]; 4577 b.append(s); 4578 if (p != null) { 4579 b.append(":"); 4580 b.append(p); 4581 } 4582 } 4583 String bs = b.toString(); 4584 idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs); 4585 ed.setId(bs); 4586 if (idList.containsKey(bs)) { 4587 if (exception || messages == null) 4588 throw new DefinitionException( 4589 "Same id '" + bs + "'on multiple elements " + idList.get(bs) + "/" + ed.getPath() + " in " + name); 4590 else 4591 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 4592 name + "." + bs, "Duplicate Element id " + bs, ValidationMessage.IssueSeverity.ERROR)); 4593 } 4594 idList.put(bs, ed.getPath()); 4595 if (ed.hasContentReference()) { 4596 String s = ed.getContentReference().substring(1); 4597 if (idMap.containsKey(s)) 4598 ed.setContentReference("#" + idMap.get(s)); 4599 4600 } 4601 } 4602 // second path - fix up any broken path based id references 4603 4604 } 4605 4606 private interface ExampleValueAccessor { 4607 Type getExampleValue(ElementDefinition ed); 4608 4609 String getId(); 4610 } 4611 4612 private class BaseExampleValueAccessor implements ExampleValueAccessor { 4613 @Override 4614 public Type getExampleValue(ElementDefinition ed) { 4615 if (ed.hasFixed()) 4616 return ed.getFixed(); 4617 if (ed.hasExample()) 4618 return ed.getExample().get(0).getValue(); 4619 else 4620 return null; 4621 } 4622 4623 @Override 4624 public String getId() { 4625 return "-genexample"; 4626 } 4627 } 4628 4629 private class ExtendedExampleValueAccessor implements ExampleValueAccessor { 4630 private String index; 4631 4632 public ExtendedExampleValueAccessor(String index) { 4633 this.index = index; 4634 } 4635 4636 @Override 4637 public Type getExampleValue(ElementDefinition ed) { 4638 if (ed.hasFixed()) 4639 return ed.getFixed(); 4640 for (Extension ex : ed.getExtension()) { 4641 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 4642 Type value = ToolingExtensions.getExtension(ex, "exValue").getValue(); 4643 if (index.equals(ndx) && value != null) 4644 return value; 4645 } 4646 return null; 4647 } 4648 4649 @Override 4650 public String getId() { 4651 return "-genexample-" + index; 4652 } 4653 } 4654 4655 public List<org.hl7.fhir.r4.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) 4656 throws FHIRException { 4657 List<org.hl7.fhir.r4.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>(); 4658 if (sd.hasSnapshot()) { 4659 if (evenWhenNoExamples || hasAnyExampleValues(sd)) 4660 examples.add(generateExample(sd, new BaseExampleValueAccessor())); 4661 for (int i = 1; i <= 50; i++) { 4662 if (hasAnyExampleValues(sd, Integer.toString(i))) 4663 examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i)))); 4664 } 4665 } 4666 return examples; 4667 } 4668 4669 private org.hl7.fhir.r4.elementmodel.Element generateExample(StructureDefinition profile, 4670 ExampleValueAccessor accessor) throws FHIRException { 4671 ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 4672 org.hl7.fhir.r4.elementmodel.Element r = new org.hl7.fhir.r4.elementmodel.Element(ed.getPath(), 4673 new Property(context, ed, profile)); 4674 List<ElementDefinition> children = getChildMap(profile, ed); 4675 for (ElementDefinition child : children) { 4676 if (child.getPath().endsWith(".id")) { 4677 org.hl7.fhir.r4.elementmodel.Element id = new org.hl7.fhir.r4.elementmodel.Element("id", 4678 new Property(context, child, profile)); 4679 id.setValue(profile.getId() + accessor.getId()); 4680 r.getChildren().add(id); 4681 } else { 4682 org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor); 4683 if (e != null) 4684 r.getChildren().add(e); 4685 } 4686 } 4687 return r; 4688 } 4689 4690 private org.hl7.fhir.r4.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, 4691 ExampleValueAccessor accessor) throws FHIRException { 4692 Type v = accessor.getExampleValue(ed); 4693 if (v != null) { 4694 return new ObjectConverter(context).convert(new Property(context, ed, profile), v); 4695 } else { 4696 org.hl7.fhir.r4.elementmodel.Element res = new org.hl7.fhir.r4.elementmodel.Element(tail(ed.getPath()), 4697 new Property(context, ed, profile)); 4698 boolean hasValue = false; 4699 List<ElementDefinition> children = getChildMap(profile, ed); 4700 for (ElementDefinition child : children) { 4701 if (!child.hasContentReference()) { 4702 org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor); 4703 if (e != null) { 4704 hasValue = true; 4705 res.getChildren().add(e); 4706 } 4707 } 4708 } 4709 if (hasValue) 4710 return res; 4711 else 4712 return null; 4713 } 4714 } 4715 4716 private boolean hasAnyExampleValues(StructureDefinition sd, String index) { 4717 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4718 for (Extension ex : ed.getExtension()) { 4719 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 4720 Extension exv = ToolingExtensions.getExtension(ex, "exValue"); 4721 if (exv != null) { 4722 Type value = exv.getValue(); 4723 if (index.equals(ndx) && value != null) 4724 return true; 4725 } 4726 } 4727 return false; 4728 } 4729 4730 private boolean hasAnyExampleValues(StructureDefinition sd) { 4731 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4732 if (ed.hasExample()) 4733 return true; 4734 return false; 4735 } 4736 4737 public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException { 4738 sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy()); 4739 4740 if (sd.hasBaseDefinition()) { 4741 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 4742 if (base == null) 4743 throw new FHIRException( 4744 "Unable to find base definition for logical model: " + sd.getBaseDefinition() + " from " + sd.getUrl()); 4745 copyElements(sd, base.getSnapshot().getElement()); 4746 } 4747 copyElements(sd, sd.getDifferential().getElement()); 4748 } 4749 4750 private void copyElements(StructureDefinition sd, List<ElementDefinition> list) { 4751 for (ElementDefinition ed : list) { 4752 if (ed.getPath().contains(".")) { 4753 ElementDefinition n = ed.copy(); 4754 n.setPath(sd.getSnapshot().getElementFirstRep().getPath() + "." 4755 + ed.getPath().substring(ed.getPath().indexOf(".") + 1)); 4756 sd.getSnapshot().addElement(n); 4757 } 4758 } 4759 } 4760 4761 public void cleanUpDifferential(StructureDefinition sd) { 4762 if (sd.getDifferential().getElement().size() > 1) 4763 cleanUpDifferential(sd, 1); 4764 } 4765 4766 private void cleanUpDifferential(StructureDefinition sd, int start) { 4767 int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.'); 4768 int c = start; 4769 int len = sd.getDifferential().getElement().size(); 4770 HashSet<String> paths = new HashSet<String>(); 4771 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) { 4772 ElementDefinition ed = sd.getDifferential().getElement().get(c); 4773 if (!paths.contains(ed.getPath())) { 4774 paths.add(ed.getPath()); 4775 int ic = c + 1; 4776 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4777 ic++; 4778 ElementDefinition slicer = null; 4779 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 4780 slices.add(ed); 4781 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) { 4782 ElementDefinition edi = sd.getDifferential().getElement().get(ic); 4783 if (ed.getPath().equals(edi.getPath())) { 4784 if (slicer == null) { 4785 slicer = new ElementDefinition(); 4786 slicer.setPath(edi.getPath()); 4787 slicer.getSlicing().setRules(SlicingRules.OPEN); 4788 sd.getDifferential().getElement().add(c, slicer); 4789 c++; 4790 ic++; 4791 } 4792 slices.add(edi); 4793 } 4794 ic++; 4795 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4796 ic++; 4797 } 4798 // now we're at the end, we're going to figure out the slicing discriminator 4799 if (slicer != null) 4800 determineSlicing(slicer, slices); 4801 } 4802 c++; 4803 if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) { 4804 cleanUpDifferential(sd, c); 4805 c++; 4806 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 4807 c++; 4808 } 4809 } 4810 } 4811 4812 private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) { 4813 // first, name them 4814 int i = 0; 4815 for (ElementDefinition ed : slices) { 4816 if (ed.hasUserData("slice-name")) { 4817 ed.setSliceName(ed.getUserString("slice-name")); 4818 } else { 4819 i++; 4820 ed.setSliceName("slice-" + Integer.toString(i)); 4821 } 4822 } 4823 // now, the hard bit, how are they differentiated? 4824 // right now, we hard code this... 4825 if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension")) 4826 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 4827 else if (slicer.getPath().equals("DiagnosticReport.result")) 4828 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code"); 4829 else if (slicer.getPath().equals("Observation.related")) 4830 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code"); 4831 else if (slicer.getPath().equals("Bundle.entry")) 4832 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile"); 4833 else 4834 throw new Error("No slicing for " + slicer.getPath()); 4835 } 4836 4837 public class SpanEntry { 4838 private List<SpanEntry> children = new ArrayList<SpanEntry>(); 4839 private boolean profile; 4840 private String id; 4841 private String name; 4842 private String resType; 4843 private String cardinality; 4844 private String description; 4845 private String profileLink; 4846 private String resLink; 4847 private String type; 4848 4849 public String getName() { 4850 return name; 4851 } 4852 4853 public void setName(String name) { 4854 this.name = name; 4855 } 4856 4857 public String getResType() { 4858 return resType; 4859 } 4860 4861 public void setResType(String resType) { 4862 this.resType = resType; 4863 } 4864 4865 public String getCardinality() { 4866 return cardinality; 4867 } 4868 4869 public void setCardinality(String cardinality) { 4870 this.cardinality = cardinality; 4871 } 4872 4873 public String getDescription() { 4874 return description; 4875 } 4876 4877 public void setDescription(String description) { 4878 this.description = description; 4879 } 4880 4881 public String getProfileLink() { 4882 return profileLink; 4883 } 4884 4885 public void setProfileLink(String profileLink) { 4886 this.profileLink = profileLink; 4887 } 4888 4889 public String getResLink() { 4890 return resLink; 4891 } 4892 4893 public void setResLink(String resLink) { 4894 this.resLink = resLink; 4895 } 4896 4897 public String getId() { 4898 return id; 4899 } 4900 4901 public void setId(String id) { 4902 this.id = id; 4903 } 4904 4905 public boolean isProfile() { 4906 return profile; 4907 } 4908 4909 public void setProfile(boolean profile) { 4910 this.profile = profile; 4911 } 4912 4913 public List<SpanEntry> getChildren() { 4914 return children; 4915 } 4916 4917 public String getType() { 4918 return type; 4919 } 4920 4921 public void setType(String type) { 4922 this.type = type; 4923 } 4924 4925 } 4926 4927 public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, 4928 String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException { 4929 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, false, true); 4930 TableModel model = initSpanningTable(gen, "", false, profile.getId()); 4931 Set<String> processed = new HashSet<String>(); 4932 SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix); 4933 4934 genSpanEntry(gen, model.getRows(), span); 4935 return gen.generate(model, "", 0, outputTracker); 4936 } 4937 4938 private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, 4939 Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException { 4940 SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile); 4941 boolean wantProcess = !processed.contains(profile.getUrl()); 4942 processed.add(profile.getUrl()); 4943 if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 4944 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 4945 if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) { 4946 String card = getCardinality(ed, profile.getSnapshot().getElement()); 4947 if (!card.endsWith(".0")) { 4948 List<String> refProfiles = listReferenceProfiles(ed); 4949 if (refProfiles.size() > 0) { 4950 String uri = refProfiles.get(0); 4951 if (uri != null) { 4952 StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri); 4953 if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT 4954 && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) { 4955 res.getChildren().add( 4956 buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix)); 4957 } 4958 } 4959 } 4960 } 4961 } 4962 } 4963 } 4964 return res; 4965 } 4966 4967 private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) { 4968 int min = ed.getMin(); 4969 int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax()); 4970 while (ed != null && ed.getPath().contains(".")) { 4971 ed = findParent(ed, list); 4972 if (ed.getMax().equals("0")) 4973 max = 0; 4974 else if (!ed.getMax().equals("1") && !ed.hasSlicing()) 4975 max = Integer.MAX_VALUE; 4976 if (ed.getMin() == 0) 4977 min = 0; 4978 } 4979 return Integer.toString(min) + ".." + (max == Integer.MAX_VALUE ? "*" : Integer.toString(max)); 4980 } 4981 4982 private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) { 4983 int i = list.indexOf(ed) - 1; 4984 while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath() + ".")) 4985 i--; 4986 if (i == -1) 4987 return null; 4988 else 4989 return list.get(i); 4990 } 4991 4992 private List<String> listReferenceProfiles(ElementDefinition ed) { 4993 List<String> res = new ArrayList<String>(); 4994 for (TypeRefComponent tr : ed.getType()) { 4995 // code is null if we're dealing with "value" and profile is null if we just 4996 // have Reference() 4997 if (tr.hasTarget() && tr.hasTargetProfile()) 4998 for (UriType u : tr.getTargetProfile()) 4999 res.add(u.getValue()); 5000 } 5001 return res; 5002 } 5003 5004 private String nameForElement(ElementDefinition ed) { 5005 return ed.getPath().substring(ed.getPath().indexOf(".") + 1); 5006 } 5007 5008 private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) 5009 throws IOException { 5010 SpanEntry res = new SpanEntry(); 5011 res.setName(name); 5012 res.setCardinality(cardinality); 5013 res.setProfileLink(profile.getUserString("path")); 5014 res.setResType(profile.getType()); 5015 StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType()); 5016 if (base != null) 5017 res.setResLink(base.getUserString("path")); 5018 res.setId(profile.getId()); 5019 res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT); 5020 StringBuilder b = new StringBuilder(); 5021 b.append(res.getResType()); 5022 boolean first = true; 5023 boolean open = false; 5024 if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 5025 res.setDescription(profile.getName()); 5026 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 5027 if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) { 5028 if (first) { 5029 open = true; 5030 first = false; 5031 b.append("["); 5032 } else { 5033 b.append(", "); 5034 } 5035 b.append(tail(ed.getBase().getPath())); 5036 b.append("="); 5037 b.append(summarize(ed.getFixed())); 5038 } 5039 } 5040 if (open) 5041 b.append("]"); 5042 } else 5043 res.setDescription("Base FHIR " + profile.getName()); 5044 res.setType(b.toString()); 5045 return res; 5046 } 5047 5048 private String summarize(Type value) throws IOException { 5049 if (value instanceof Coding) 5050 return summarizeCoding((Coding) value); 5051 else if (value instanceof CodeableConcept) 5052 return summarizeCodeableConcept((CodeableConcept) value); 5053 else 5054 return buildJson(value); 5055 } 5056 5057 private String summarizeCoding(Coding value) { 5058 String uri = value.getSystem(); 5059 String system = NarrativeGenerator.describeSystem(uri); 5060 if (Utilities.isURL(system)) { 5061 if (system.equals("http://cap.org/protocols")) 5062 system = "CAP Code"; 5063 } 5064 return system + " " + value.getCode(); 5065 } 5066 5067 private String summarizeCodeableConcept(CodeableConcept value) { 5068 if (value.hasCoding()) 5069 return summarizeCoding(value.getCodingFirstRep()); 5070 else 5071 return value.getText(); 5072 } 5073 5074 private boolean isKeyProperty(String path) { 5075 return Utilities.existsInList(path, "Observation.code"); 5076 } 5077 5078 public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) { 5079 TableModel model = gen.new TableModel(id, false); 5080 5081 model.setDocoImg(prefix + "help16.png"); 5082 model.setDocoRef(prefix + "formats.html#table"); // todo: change to graph definition 5083 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0)); 5084 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Card.", 5085 "Minimum and Maximum # of times the the element can appear in the instance", null, 0)); 5086 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0)); 5087 model.getTitles() 5088 .add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0)); 5089 return model; 5090 } 5091 5092 private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException { 5093 Row row = gen.new Row(); 5094 rows.add(row); 5095 row.setAnchor(span.getId()); 5096 // row.setColor(..?); 5097 if (span.isProfile()) 5098 row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE); 5099 else 5100 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 5101 5102 row.getCells().add(gen.new Cell(null, null, span.getName(), null, null)); 5103 row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null)); 5104 row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null)); 5105 row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null)); 5106 5107 for (SpanEntry child : span.getChildren()) 5108 genSpanEntry(gen, row.getSubRows(), child); 5109 } 5110 5111 public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, 5112 boolean isExists) { 5113 if (discriminator.endsWith("@pattern")) 5114 return makeDiscriminator(DiscriminatorType.PATTERN, 5115 discriminator.length() == 8 ? "" : discriminator.substring(0, discriminator.length() - 9)); 5116 if (discriminator.endsWith("@profile")) 5117 return makeDiscriminator(DiscriminatorType.PROFILE, 5118 discriminator.length() == 8 ? "" : discriminator.substring(0, discriminator.length() - 9)); 5119 if (discriminator.endsWith("@type")) 5120 return makeDiscriminator(DiscriminatorType.TYPE, 5121 discriminator.length() == 5 ? "" : discriminator.substring(0, discriminator.length() - 6)); 5122 if (discriminator.endsWith("@exists")) 5123 return makeDiscriminator(DiscriminatorType.EXISTS, 5124 discriminator.length() == 7 ? "" : discriminator.substring(0, discriminator.length() - 8)); 5125 if (isExists) 5126 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 5127 return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator); 5128 } 5129 5130 private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) { 5131 return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType) 5132 .setPath(Utilities.noString(str) ? "$this" : str); 5133 } 5134 5135 public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException { 5136 switch (t.getType()) { 5137 case PROFILE: 5138 return t.getPath() + "/@profile"; 5139 case TYPE: 5140 return t.getPath() + "/@type"; 5141 case VALUE: 5142 return t.getPath(); 5143 case PATTERN: 5144 return t.getPath(); 5145 case EXISTS: 5146 return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - 5147 // one with minOccurs=1 and other with maxOccur=0 5148 default: 5149 throw new FHIRException("Unable to represent " + t.getType().toCode() + ":" + t.getPath() + " in R2"); 5150 } 5151 } 5152 5153 public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) { 5154 String epath = url.substring(54); 5155 if (!epath.contains(".")) 5156 return null; 5157 String type = epath.substring(0, epath.indexOf(".")); 5158 StructureDefinition sd = context.fetchTypeDefinition(type); 5159 if (sd == null) 5160 return null; 5161 ElementDefinition ed = null; 5162 for (ElementDefinition t : sd.getSnapshot().getElement()) { 5163 if (t.getPath().equals(epath)) { 5164 ed = t; 5165 break; 5166 } 5167 } 5168 if (ed == null) 5169 return null; 5170 if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) { 5171 return null; 5172 } else { 5173 StructureDefinition template = context.fetchResource(StructureDefinition.class, 5174 "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"); 5175 StructureDefinition ext = template.copy(); 5176 ext.setUrl(url); 5177 ext.setId("extension-" + epath); 5178 ext.setName("Extension-" + epath); 5179 ext.setTitle("Extension for r4 " + epath); 5180 ext.setStatus(sd.getStatus()); 5181 ext.setDate(sd.getDate()); 5182 ext.getContact().clear(); 5183 ext.getContact().addAll(sd.getContact()); 5184 ext.setFhirVersion(sd.getFhirVersion()); 5185 ext.setDescription(ed.getDefinition()); 5186 ext.getContext().clear(); 5187 ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf("."))); 5188 ext.getDifferential().getElement().clear(); 5189 ext.getSnapshot().getElement().get(3).setFixed(new UriType(url)); 5190 ext.getSnapshot().getElement().set(4, ed.copy()); 5191 ext.getSnapshot().getElement().get(4).setPath("Extension.value" + Utilities.capitalize(ed.typeSummary())); 5192 return ext; 5193 } 5194 5195 } 5196 5197 public boolean isThrowException() { 5198 return exception; 5199 } 5200 5201 public void setThrowException(boolean exception) { 5202 this.exception = exception; 5203 } 5204 5205 public TerminologyServiceOptions getTerminologyServiceOptions() { 5206 return terminologyServiceOptions; 5207 } 5208 5209 public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) { 5210 this.terminologyServiceOptions = terminologyServiceOptions; 5211 } 5212 5213 public boolean isNewSlicingProcessing() { 5214 return newSlicingProcessing; 5215 } 5216 5217 public void setNewSlicingProcessing(boolean newSlicingProcessing) { 5218 this.newSlicingProcessing = newSlicingProcessing; 5219 } 5220 5221 5222 public static boolean isExtensionDefinition(StructureDefinition sd) { 5223 return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension"); 5224 } 5225 5226 public static boolean isSimpleExtension(StructureDefinition sd) { 5227 if (!isExtensionDefinition(sd)) { 5228 return false; 5229 } 5230 ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); 5231 return value != null && !value.isProhibited(); 5232 } 5233 5234 public static boolean isComplexExtension(StructureDefinition sd) { 5235 if (!isExtensionDefinition(sd)) { 5236 return false; 5237 } 5238 ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); 5239 return value == null || value.isProhibited(); 5240 } 5241 5242 public static boolean isModifierExtension(StructureDefinition sd) { 5243 ElementDefinition defn = sd.getSnapshot().getElementByPath("Extension"); 5244 return defn.getIsModifier(); 5245 } 5246 5247}