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