001package org.hl7.fhir.r5.conformance.profile; 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 032 033import java.io.IOException; 034import java.io.OutputStream; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.Collections; 038import java.util.Comparator; 039import java.util.Date; 040import java.util.HashMap; 041import java.util.HashSet; 042import java.util.Iterator; 043import java.util.List; 044import java.util.Map; 045import java.util.Set; 046 047import org.hl7.fhir.exceptions.DefinitionException; 048import org.hl7.fhir.exceptions.FHIRException; 049import org.hl7.fhir.exceptions.FHIRFormatError; 050import org.hl7.fhir.r5.conformance.ElementRedirection; 051import org.hl7.fhir.r5.context.IWorkerContext; 052import org.hl7.fhir.r5.elementmodel.ObjectConverter; 053import org.hl7.fhir.r5.elementmodel.Property; 054import org.hl7.fhir.r5.model.Base; 055import org.hl7.fhir.r5.model.BooleanType; 056import org.hl7.fhir.r5.model.Coding; 057import org.hl7.fhir.r5.model.DataType; 058import org.hl7.fhir.r5.model.ElementDefinition; 059import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; 060import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBaseComponent; 061import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 062import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; 063import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent; 064import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; 065import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; 066import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 067import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; 068import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 069import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 070import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; 071import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 072import org.hl7.fhir.r5.model.ExpressionNode; 073import org.hl7.fhir.r5.model.ExpressionNode.Kind; 074import org.hl7.fhir.r5.model.ExpressionNode.Operation; 075import org.hl7.fhir.r5.model.Extension; 076import org.hl7.fhir.r5.model.IdType; 077import org.hl7.fhir.r5.model.Resource; 078import org.hl7.fhir.r5.model.StringType; 079import org.hl7.fhir.r5.model.StructureDefinition; 080import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType; 081import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent; 082import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent; 083import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 084import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent; 085import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; 086import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 087import org.hl7.fhir.r5.model.UriType; 088import org.hl7.fhir.r5.model.ValueSet; 089import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 090import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 091import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 092import org.hl7.fhir.r5.utils.FHIRPathEngine; 093import org.hl7.fhir.r5.utils.ToolingExtensions; 094import org.hl7.fhir.r5.utils.TranslatingUtilities; 095import org.hl7.fhir.r5.utils.XVerExtensionManager; 096import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus; 097import org.hl7.fhir.r5.utils.formats.CSVWriter; 098import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 099import org.hl7.fhir.utilities.Utilities; 100import org.hl7.fhir.utilities.VersionUtilities; 101import org.hl7.fhir.utilities.i18n.I18nConstants; 102import org.hl7.fhir.utilities.validation.ValidationMessage; 103import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 104import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 105import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 106import org.hl7.fhir.utilities.validation.ValidationOptions; 107import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 108import org.hl7.fhir.utilities.xml.SchematronWriter; 109import org.hl7.fhir.utilities.xml.SchematronWriter.Rule; 110import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType; 111import org.hl7.fhir.utilities.xml.SchematronWriter.Section; 112 113/** 114 * This class provides a set of utility operations for working with Profiles. 115 * Key functionality: 116 * * getChildMap --? 117 * * getChildList 118 * * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 119 * * closeDifferential: fill out a differential by excluding anything not mentioned 120 * * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions 121 * * generateTable: generate the HTML for a hierarchical table presentation of a structure 122 * * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point 123 * * summarize: describe the contents of a profile 124 * 125 * note to maintainers: Do not make modifications to the snapshot generation without first changing the snapshot generation test cases to demonstrate the grounds for your change 126 * 127 * @author Grahame 128 * 129 */ 130public class ProfileUtilities extends TranslatingUtilities { 131 132 private static final List<String> INHERITED_ED_URLS = Arrays.asList( 133 "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-binding-style", 134 "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-extension-style"); 135 136 public IWorkerContext getContext() { 137 return this.context; 138 } 139 140 public static class SourcedChildDefinitions { 141 private StructureDefinition source; 142 private List<ElementDefinition> list; 143 public SourcedChildDefinitions(StructureDefinition source, List<ElementDefinition> list) { 144 super(); 145 this.source = source; 146 this.list = list; 147 } 148 public StructureDefinition getSource() { 149 return source; 150 } 151 public List<ElementDefinition> getList() { 152 return list; 153 } 154 } 155 156 public class ElementDefinitionResolution { 157 158 private StructureDefinition source; 159 private ElementDefinition element; 160 161 public ElementDefinitionResolution(StructureDefinition source, ElementDefinition element) { 162 this.source = source; 163 this.element = element; 164 } 165 166 public StructureDefinition getSource() { 167 return source; 168 } 169 170 public ElementDefinition getElement() { 171 return element; 172 } 173 174 } 175 176 public static class ElementChoiceGroup { 177 private Row row; 178 private String name; 179 private boolean mandatory; 180 private List<String> elements = new ArrayList<>(); 181 182 public ElementChoiceGroup(String name, boolean mandatory) { 183 super(); 184 this.name = name; 185 this.mandatory = mandatory; 186 } 187 public Row getRow() { 188 return row; 189 } 190 public List<String> getElements() { 191 return elements; 192 } 193 public void setRow(Row row) { 194 this.row = row; 195 } 196 public String getName() { 197 return name; 198 } 199 public boolean isMandatory() { 200 return mandatory; 201 } 202 public void setMandatory(boolean mandatory) { 203 this.mandatory = mandatory; 204 } 205 206 } 207 208 private static final int MAX_RECURSION_LIMIT = 10; 209 210 public static class ExtensionContext { 211 212 private ElementDefinition element; 213 private StructureDefinition defn; 214 215 public ExtensionContext(StructureDefinition ext, ElementDefinition ed) { 216 this.defn = ext; 217 this.element = ed; 218 } 219 220 public ElementDefinition getElement() { 221 return element; 222 } 223 224 public StructureDefinition getDefn() { 225 return defn; 226 } 227 228 public String getUrl() { 229 if (element == defn.getSnapshot().getElement().get(0)) 230 return defn.getUrl(); 231 else 232 return element.getSliceName(); 233 } 234 235 public ElementDefinition getExtensionValueDefinition() { 236 int i = defn.getSnapshot().getElement().indexOf(element)+1; 237 while (i < defn.getSnapshot().getElement().size()) { 238 ElementDefinition ed = defn.getSnapshot().getElement().get(i); 239 if (ed.getPath().equals(element.getPath())) 240 return null; 241 if (ed.getPath().startsWith(element.getPath()+".value") && !ed.hasSlicing()) 242 return ed; 243 i++; 244 } 245 return null; 246 } 247 } 248 249 public static final String UD_BASE_MODEL = "base.model"; 250 public static final String UD_BASE_PATH = "base.path"; 251 public static final String UD_DERIVATION_EQUALS = "derivation.equals"; 252 public static final String UD_DERIVATION_POINTER = "derived.pointer"; 253 public static final String UD_IS_DERIVED = "derived.fact"; 254 public static final String UD_GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed"; 255 private static final boolean COPY_BINDING_EXTENSIONS = false; 256 private static final boolean DONT_DO_THIS = false; 257 258 private boolean debug; 259 // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here 260 private final IWorkerContext context; 261 private FHIRPathEngine fpe; 262 private List<ValidationMessage> messages; 263 private List<String> snapshotStack = new ArrayList<String>(); 264 private ProfileKnowledgeProvider pkp; 265// private boolean igmode; 266 private boolean exception; 267 private ValidationOptions terminologyServiceOptions = new ValidationOptions(); 268 private boolean newSlicingProcessing; 269 private String defWebRoot; 270 private boolean autoFixSliceNames; 271 private XVerExtensionManager xver; 272 private boolean wantFixDifferentialFirstElementType; 273 private Set<String> masterSourceFileNames; 274 private Map<ElementDefinition, SourcedChildDefinitions> childMapCache = new HashMap<>(); 275 276 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) { 277 super(); 278 this.context = context; 279 this.messages = messages; 280 this.pkp = pkp; 281 282 this.fpe = fpe; 283 if (context != null && this.fpe == null) { 284 this.fpe = new FHIRPathEngine(context, this); 285 } 286 } 287 288 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) { 289 super(); 290 this.context = context; 291 this.messages = messages; 292 this.pkp = pkp; 293 if (context != null) { 294 this.fpe = new FHIRPathEngine(context, this); 295 } 296 } 297 298 public boolean isWantFixDifferentialFirstElementType() { 299 return wantFixDifferentialFirstElementType; 300 } 301 302 public void setWantFixDifferentialFirstElementType(boolean wantFixDifferentialFirstElementType) { 303 this.wantFixDifferentialFirstElementType = wantFixDifferentialFirstElementType; 304 } 305 306 public boolean isAutoFixSliceNames() { 307 return autoFixSliceNames; 308 } 309 310 public ProfileUtilities setAutoFixSliceNames(boolean autoFixSliceNames) { 311 this.autoFixSliceNames = autoFixSliceNames; 312 return this; 313 } 314 315 public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException { 316 if (childMapCache.containsKey(element)) { 317 return childMapCache.get(element); 318 } 319 StructureDefinition src = profile; 320 if (element.getContentReference() != null) { 321 List<ElementDefinition> list = null; 322 String id = null; 323 if (element.getContentReference().startsWith("#")) { 324 // internal reference 325 id = element.getContentReference().substring(1); 326 list = profile.getSnapshot().getElement(); 327 } else if (element.getContentReference().contains("#")) { 328 // external reference 329 String ref = element.getContentReference(); 330 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ref.substring(0, ref.indexOf("#")), profile); 331 if (sd == null) { 332 throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'"); 333 } 334 src = sd; 335 list = sd.getSnapshot().getElement(); 336 id = ref.substring(ref.indexOf("#")+1); 337 } else { 338 throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'"); 339 } 340 341 for (ElementDefinition e : list) { 342 if (id.equals(e.getId())) 343 return getChildMap(profile, e); 344 } 345 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_NAME_REFERENCE__AT_PATH_, element.getContentReference(), element.getPath())); 346 347 } else { 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 + ".")) { 354 // We only want direct children, not all descendants 355 if (!e.getPath().substring(path.length()+1).contains(".")) 356 res.add(e); 357 } else 358 break; 359 } 360 SourcedChildDefinitions result = new SourcedChildDefinitions(src, res); 361 childMapCache.put(element, result); 362 return result; 363 } 364 } 365 366 367 public List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException { 368 if (!element.hasSlicing()) 369 throw new Error(context.formatMessage(I18nConstants.GETSLICELIST_SHOULD_ONLY_BE_CALLED_WHEN_THE_ELEMENT_HAS_SLICING)); 370 371 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 372 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 373 String path = element.getPath(); 374 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 375 ElementDefinition e = elements.get(index); 376 if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) { 377 // We want elements with the same path (until we hit an element that doesn't start with the same path) 378 if (e.getPath().equals(element.getPath())) 379 res.add(e); 380 } else 381 break; 382 } 383 return res; 384 } 385 386 387 /** 388 * Given a Structure, navigate to the element given by the path and return the direct children of that element 389 * 390 * @param profile The structure to navigate into 391 * @param path The path of the element within the structure to get the children for 392 * @return A List containing the element children (all of them are Elements) 393 */ 394 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) { 395 return getChildList(profile, path, id, false); 396 } 397 398 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff) { 399 return getChildList(profile, path, id, diff, false); 400 } 401 402 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff, boolean refs) { 403 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 404 405 boolean capturing = id==null; 406 if (id==null && !path.contains(".")) 407 capturing = true; 408 409 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 410 for (ElementDefinition e : list) { 411 if (e == null) 412 throw new Error(context.formatMessage(I18nConstants.ELEMENT__NULL_, profile.getUrl())); 413 if (e.getId() == null) 414 throw new Error(context.formatMessage(I18nConstants.ELEMENT_ID__NULL__ON_, e.toString(), profile.getUrl())); 415 416 if (!capturing && id!=null && e.getId().equals(id)) { 417 capturing = true; 418 } 419 420 // If our element is a slice, stop capturing children as soon as we see the next slice 421 if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path)) 422 break; 423 424 if (capturing) { 425 String p = e.getPath(); 426 427 if (refs && !Utilities.noString(e.getContentReference()) && path.startsWith(p)) { 428 if (path.length() > p.length()) { 429 return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff); 430 } else if (e.getContentReference().startsWith("#")) { 431 return getChildList(profile, e.getContentReference().substring(1), null, diff); 432 } else if (e.getContentReference().contains("#")) { 433 String url = e.getContentReference().substring(0, e.getContentReference().indexOf("#")); 434 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url, profile); 435 if (sd == null) { 436 throw new DefinitionException("Unable to find Structure "+url); 437 } 438 return getChildList(sd, e.getContentReference().substring(e.getContentReference().indexOf("#")+1), null, diff); 439 } else { 440 return getChildList(profile, e.getContentReference(), null, diff); 441 } 442 443 } else if (p.startsWith(path+".") && !p.equals(path)) { 444 String tail = p.substring(path.length()+1); 445 if (!tail.contains(".")) { 446 res.add(e); 447 } 448 } 449 } 450 } 451 452 return res; 453 } 454 455 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff, boolean refs) { 456 return getChildList(structure, element.getPath(), element.getId(), diff, refs); 457 } 458 459 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) { 460 return getChildList(structure, element.getPath(), element.getId(), diff); 461 } 462 463 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) { 464 if (element.hasContentReference()) { 465 ElementDefinition target = element; 466 for (ElementDefinition t : structure.getSnapshot().getElement()) { 467 if (t.getId().equals(element.getContentReference().substring(1))) { 468 target = t; 469 } 470 } 471 return getChildList(structure, target.getPath(), target.getId(), false); 472 } else { 473 return getChildList(structure, element.getPath(), element.getId(), false); 474 } 475 } 476 477 private void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException { 478 if (base == null) 479 throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED)); 480 if (derived == null) 481 throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED)); 482 483 for (StructureDefinitionMappingComponent baseMap : base.getMapping()) { 484 boolean found = false; 485 for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) { 486 if (derivedMap.getUri() != null && derivedMap.getUri().equals(baseMap.getUri())) { 487 found = true; 488 break; 489 } 490 } 491 if (!found) { 492 derived.getMapping().add(baseMap); 493 } 494 } 495 } 496 497 /** 498 * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 499 * 500 * @param base - the base structure on which the differential will be applied 501 * @param derived - the differential to apply to the base 502 * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL (e.g. the canonical URL) 503 * @param webUrl - where the base has relative urls in markdown, these need to be converted to absolutes by prepending this URL (this is not the same as the canonical URL) 504 * @return 505 * @throws FHIRException 506 * @throws DefinitionException 507 * @throws Exception 508 */ 509 public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException { 510 if (base == null) { 511 throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED)); 512 } 513 if (derived == null) { 514 throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED)); 515 } 516 checkNotGenerating(base, "Base for generating a snapshot for the profile "+derived.getUrl()); 517 checkNotGenerating(derived, "Focus for generating a snapshot"); 518 519 if (!base.hasType()) { 520 throw new DefinitionException(context.formatMessage(I18nConstants.BASE_PROFILE__HAS_NO_TYPE, base.getUrl())); 521 } 522 if (!derived.hasType()) { 523 throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_TYPE, derived.getUrl())); 524 } 525 if (!derived.hasDerivation()) { 526 throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_DERIVATION_VALUE_AND_SO_CANT_BE_PROCESSED, derived.getUrl())); 527 } 528 if (!base.getType().equals(derived.getType()) && derived.getDerivation() == TypeDerivationRule.CONSTRAINT) { 529 throw new DefinitionException(context.formatMessage(I18nConstants.BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___, base.getUrl(), base.getType(), derived.getUrl(), derived.getType())); 530 } 531 532 fixTypeOfResourceId(base); 533 534 if (snapshotStack.contains(derived.getUrl())) { 535 throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString())); 536 } 537 derived.setUserData("profileutils.snapshot.generating", true); 538 snapshotStack.add(derived.getUrl()); 539 try { 540 541 if (!Utilities.noString(webUrl) && !webUrl.endsWith("/")) 542 webUrl = webUrl + '/'; 543 544 if (defWebRoot == null) 545 defWebRoot = webUrl; 546 derived.setSnapshot(new StructureDefinitionSnapshotComponent()); 547 548 try { 549 checkDifferential(derived.getDifferential().getElement(), derived.getTypeName(), derived.getUrl()); 550 checkDifferentialBaseType(derived); 551 552 copyInheritedExtensions(base, derived); 553 // so we have two lists - the base list, and the differential list 554 // the differential list is only allowed to include things that are in the base list, but 555 // is allowed to include them multiple times - thereby slicing them 556 557 // our approach is to walk through the base list, and see whether the differential 558 // says anything about them. 559 // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths 560 561 562 for (ElementDefinition e : derived.getDifferential().getElement()) 563 e.clearUserData(UD_GENERATED_IN_SNAPSHOT); 564 565 // we actually delegate the work to a subroutine so we can re-enter it with a different cursors 566 StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential()); // we make a copy here because we're sometimes going to hack the differential while processing it. Have to migrate user data back afterwards 567 568 StructureDefinitionSnapshotComponent baseSnapshot = base.getSnapshot(); 569 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 570 String derivedType = derived.getTypeName(); 571 572 baseSnapshot = cloneSnapshot(baseSnapshot, base.getTypeName(), derivedType); 573 } 574 // if (derived.getId().equals("2.16.840.1.113883.10.20.22.2.1.1")) { 575 // debug = true; 576 // } 577 578 ProfilePathProcessor.processPaths(this, base, derived, url, webUrl, diff, baseSnapshot); 579 580 checkGroupConstraints(derived); 581 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 582 for (ElementDefinition e : diff.getElement()) { 583 if (!e.hasUserData(UD_GENERATED_IN_SNAPSHOT) && e.getPath().contains(".")) { 584 ElementDefinition outcome = updateURLs(url, webUrl, e.copy()); 585 e.setUserData(UD_GENERATED_IN_SNAPSHOT, outcome); 586 derived.getSnapshot().addElement(outcome); 587 if (walksInto(diff.getElement(), e)) { 588 if (e.getType().size() > 1) { 589 throw new DefinitionException("Unsupported scenario: specialization walks into multiple types at "+e.getId()); 590 } else { 591 addInheritedElementsForSpecialization(derived.getSnapshot(), outcome, outcome.getTypeFirstRep().getWorkingCode(), outcome.getPath(), url, webUrl); 592 } 593 } 594 } 595 } 596 } 597 598 if (derived.getKind() != StructureDefinitionKind.LOGICAL && !derived.getSnapshot().getElementFirstRep().getType().isEmpty()) 599 throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl())); 600 updateMaps(base, derived); 601 602 setIds(derived, false); 603 if (debug) { 604 System.out.println("Differential: "); 605 for (ElementDefinition ed : derived.getDifferential().getElement()) 606 System.out.println(" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 607 System.out.println("Snapshot: "); 608 for (ElementDefinition ed : derived.getSnapshot().getElement()) 609 System.out.println(" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 610 } 611 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 612 //Check that all differential elements have a corresponding snapshot element 613 int ce = 0; 614 for (ElementDefinition e : diff.getElement()) { 615 if (!e.hasUserData("diff-source")) 616 throw new Error(context.formatMessage(I18nConstants.UNXPECTED_INTERNAL_CONDITION__NO_SOURCE_ON_DIFF_ELEMENT)); 617 else { 618 if (e.hasUserData(UD_DERIVATION_EQUALS)) 619 ((Base) e.getUserData("diff-source")).setUserData(UD_DERIVATION_EQUALS, e.getUserData(UD_DERIVATION_EQUALS)); 620 if (e.hasUserData(UD_DERIVATION_POINTER)) 621 ((Base) e.getUserData("diff-source")).setUserData(UD_DERIVATION_POINTER, e.getUserData(UD_DERIVATION_POINTER)); 622 } 623 if (!e.hasUserData(UD_GENERATED_IN_SNAPSHOT)) { 624 b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath()); 625 ce++; 626 if (e.hasId()) { 627 String msg = "No match found in the generated snapshot: check that the path and definitions are legal in the differential (including order)"; 628 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+e.getId(), msg, ValidationMessage.IssueSeverity.ERROR)); 629 } 630 } 631 } 632 if (!Utilities.noString(b.toString())) { 633 String msg = "The profile "+derived.getUrl()+" has "+ce+" "+Utilities.pluralize("element", ce)+" in the differential ("+b.toString()+") that don't have a matching element in the snapshot: check that the path and definitions are legal in the differential (including order)"; 634 if (debug) { 635 System.out.println("Error in snapshot generation: "+msg); 636 if (!debug) { 637 System.out.println("Differential: "); 638 for (ElementDefinition ed : derived.getDifferential().getElement()) 639 System.out.println(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 640 System.out.println("Snapshot: "); 641 for (ElementDefinition ed : derived.getSnapshot().getElement()) 642 System.out.println(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 643 } 644 } 645 if (exception) 646 throw new DefinitionException(msg); 647 else 648 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, msg, ValidationMessage.IssueSeverity.ERROR)); 649 } 650 // hack around a problem in R4 definitions (somewhere?) 651 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 652 for (ElementDefinitionMappingComponent mm : ed.getMapping()) { 653 if (mm.hasMap()) { 654 mm.setMap(mm.getMap().trim()); 655 } 656 } 657 for (ElementDefinitionConstraintComponent s : ed.getConstraint()) { 658 if (s.hasSource()) { 659 String ref = s.getSource(); 660 if (!Utilities.isAbsoluteUrl(ref)) { 661 if (ref.contains(".")) { 662 s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref.substring(0, ref.indexOf("."))+"#"+ref); 663 } else { 664 s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref); 665 } 666 } 667 } 668 } 669 } 670 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 671 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 672 if (!ed.hasBase()) { 673 ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax()); 674 } 675 } 676 } 677 // last, check for wrong profiles or target profiles 678 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 679 for (TypeRefComponent t : ed.getType()) { 680 for (UriType u : t.getProfile()) { 681 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue(), derived); 682 if (sd == null) { 683 if (xver != null && xver.matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) { 684 sd = xver.makeDefinition(u.getValue()); 685 } 686 } 687 if (sd == null) { 688 if (messages != null) { 689 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING)); 690 } 691 } else { 692 String wt = t.getWorkingCode(); 693 if (ed.getPath().equals("Bundle.entry.response.outcome")) { 694 wt = "OperationOutcome"; 695 } 696 if (!sd.getType().equals(wt)) { 697 boolean ok = isCompatibleType(wt, sd); 698 if (!ok) { 699 String smsg = "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt; 700 if (exception) 701 throw new DefinitionException(smsg); 702 else 703 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), smsg, IssueSeverity.ERROR)); 704 } 705 } 706 } 707 } 708 } 709 } 710 } catch (Exception e) { 711 // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind 712 derived.setSnapshot(null); 713 derived.clearUserData("profileutils.snapshot.generating"); 714 throw e; 715 } 716 } finally { 717 derived.clearUserData("profileutils.snapshot.generating"); 718 snapshotStack.remove(derived.getUrl()); 719 } 720 } 721 722 723 724 725 private void copyInheritedExtensions(StructureDefinition base, StructureDefinition derived) { 726 for (Extension ext : base.getExtension()) { 727 if (Utilities.existsInList(ext.getUrl(), INHERITED_ED_URLS) && !derived.hasExtension(ext.getUrl())) { 728 derived.getExtension().add(ext.copy()); 729 } 730 } 731 732 } 733 734 private void addInheritedElementsForSpecialization(StructureDefinitionSnapshotComponent snapshot, ElementDefinition focus, String type, String path, String url, String weburl) { 735 StructureDefinition sd = context.fetchTypeDefinition(type); 736 if (sd != null) { 737 // don't do this. should already be in snapshot ... addInheritedElementsForSpecialization(snapshot, focus, sd.getBaseDefinition(), path, url, weburl); 738 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 739 if (ed.getPath().contains(".")) { 740 ElementDefinition outcome = updateURLs(url, weburl, ed.copy()); 741 outcome.setPath(outcome.getPath().replace(sd.getTypeName(), path)); 742 snapshot.getElement().add(outcome); 743 } else { 744 focus.getConstraint().addAll(ed.getConstraint()); 745 for (Extension ext : ed.getExtension()) { 746 if (Utilities.existsInList(ext.getUrl(), INHERITED_ED_URLS) && !focus.hasExtension(ext.getUrl())) { 747 focus.getExtension().add(ext.copy()); 748 } 749 } 750 } 751 } 752 } 753 } 754 755 private boolean walksInto(List<ElementDefinition> list, ElementDefinition ed) { 756 int i = list.indexOf(ed); 757 return (i < list.size() - 1) && list.get(i + 1).getPath().startsWith(ed.getPath()+"."); 758 } 759 760 private void fixTypeOfResourceId(StructureDefinition base) { 761 if (base.getKind() == StructureDefinitionKind.RESOURCE && (base.getFhirVersion() == null || VersionUtilities.isR4Plus(base.getFhirVersion().toCode()))) { 762 fixTypeOfResourceId(base.getSnapshot().getElement()); 763 fixTypeOfResourceId(base.getDifferential().getElement()); 764 } 765 } 766 767 private void fixTypeOfResourceId(List<ElementDefinition> list) { 768 for (ElementDefinition ed : list) { 769 if (ed.hasBase() && ed.getBase().getPath().equals("Resource.id")) { 770 for (TypeRefComponent tr : ed.getType()) { 771 tr.setCode("http://hl7.org/fhirpath/System.String"); 772 tr.removeExtension(ToolingExtensions.EXT_FHIR_TYPE); 773 ToolingExtensions.addUrlExtension(tr, ToolingExtensions.EXT_FHIR_TYPE, "id"); 774 } 775 } 776 } 777 } 778 779 /** 780 * Check if derived has the correct base type 781 * 782 * Clear first element of differential under certain conditions. 783 * 784 * @param derived 785 * @throws Error 786 */ 787 private void checkDifferentialBaseType(StructureDefinition derived) throws Error { 788 if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) { 789 if (wantFixDifferentialFirstElementType && typeMatchesAncestor(derived.getDifferential().getElementFirstRep().getType(), derived.getBaseDefinition(), derived)) { 790 derived.getDifferential().getElementFirstRep().getType().clear(); 791 } else if (derived.getKind() != StructureDefinitionKind.LOGICAL) { 792 throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_DIFFERENTIAL_ELEMENT)); 793 } 794 } 795 } 796 797 private boolean typeMatchesAncestor(List<TypeRefComponent> type, String baseDefinition, Resource src) { 798 StructureDefinition sd = context.fetchResource(StructureDefinition.class, baseDefinition, src); 799 return sd != null && type.size() == 1 && sd.getType().equals(type.get(0).getCode()); 800 } 801 802 803 private void checkGroupConstraints(StructureDefinition derived) { 804 List<ElementDefinition> toRemove = new ArrayList<>(); 805// List<ElementDefinition> processed = new ArrayList<>(); 806 for (ElementDefinition element : derived.getSnapshot().getElement()) { 807 if (!toRemove.contains(element) && !element.hasSlicing() && !"0".equals(element.getMax())) { 808 checkForChildrenInGroup(derived, toRemove, element); 809 } 810 } 811 derived.getSnapshot().getElement().removeAll(toRemove); 812 } 813 814 private void checkForChildrenInGroup(StructureDefinition derived, List<ElementDefinition> toRemove, ElementDefinition element) throws Error { 815 List<ElementDefinition> children = getChildren(derived, element); 816 List<ElementChoiceGroup> groups = readChoices(element, children); 817 for (ElementChoiceGroup group : groups) { 818// System.out.println(children); 819 String mandated = null; 820 Set<String> names = new HashSet<>(); 821 for (ElementDefinition ed : children) { 822 String name = tail(ed.getPath()); 823 if (names.contains(name)) { 824 throw new Error("huh?"); 825 } else { 826 names.add(name); 827 } 828 if (group.getElements().contains(name)) { 829 if (ed.getMin() == 1) { 830 if (mandated == null) { 831 mandated = name; 832 } else { 833 throw new Error("Error: there are two mandatory elements in "+derived.getUrl()+" when there can only be one: "+mandated+" and "+name); 834 } 835 } 836 } 837 } 838 if (mandated != null) { 839 for (ElementDefinition ed : children) { 840 String name = tail(ed.getPath()); 841 if (group.getElements().contains(name) && !mandated.equals(name)) { 842 ed.setMax("0"); 843 addAllChildren(derived, ed, toRemove); 844 } 845 } 846 } 847 } 848 } 849 850 private List<ElementDefinition> getChildren(StructureDefinition derived, ElementDefinition element) { 851 List<ElementDefinition> elements = derived.getSnapshot().getElement(); 852 int index = elements.indexOf(element) + 1; 853 String path = element.getPath()+"."; 854 List<ElementDefinition> list = new ArrayList<>(); 855 while (index < elements.size()) { 856 ElementDefinition e = elements.get(index); 857 String p = e.getPath(); 858 if (p.startsWith(path) && !e.hasSliceName()) { 859 if (!p.substring(path.length()).contains(".")) { 860 list.add(e); 861 } 862 index++; 863 } else { 864 break; 865 } 866 } 867 return list; 868 } 869 870 private void addAllChildren(StructureDefinition derived, ElementDefinition element, List<ElementDefinition> toRemove) { 871 List<ElementDefinition> children = getChildList(derived, element); 872 for (ElementDefinition child : children) { 873 toRemove.add(child); 874 addAllChildren(derived, child, toRemove); 875 } 876 } 877 878 /** 879 * Check that a differential is valid. 880 * @param elements 881 * @param type 882 * @param url 883 */ 884 private void checkDifferential(List<ElementDefinition> elements, String type, String url) { 885 boolean first = true; 886 for (ElementDefinition ed : elements) { 887 if (!ed.hasPath()) { 888 throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url)); 889 } 890 String p = ed.getPath(); 891 if (p == null) { 892 throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_VALUE_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url)); 893 } 894 if (!((first && type.equals(p)) || p.startsWith(type+"."))) { 895 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__MUST_START_WITH_, p, url, type, (first ? " (or be '"+type+"')" : ""))); 896 } 897 if (p.contains(".")) { 898 // Element names (the parts of a path delineated by the '.' character) SHALL NOT contain whitespace (i.e. Unicode characters marked as whitespace) 899 // Element names SHALL NOT contain the characters ,:;'"/|?!@#$%^&*()[]{} 900 // Element names SHOULD not contain non-ASCII characters 901 // Element names SHALL NOT exceed 64 characters in length 902 String[] pl = p.split("\\."); 903 for (String pp : pl) { 904 if (pp.length() < 1) { 905 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_MISING_, p, url)); 906 } 907 if (pp.length() > 64) { 908 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_EXCEEDS_64_CHARS_IN_LENGTH, p, url)); 909 } 910 for (char ch : pp.toCharArray()) { 911 if (Character.isWhitespace(ch)) { 912 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NO_UNICODE_WHITESPACE, p, url)); 913 } 914 if (Utilities.existsInList(ch, ',', ':', ';', '\'', '"', '/', '|', '?', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '{', '}')) { 915 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch)); 916 } 917 if (ch < ' ' || ch > 'z') { 918 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch)); 919 } 920 } 921 if (pp.contains("[") || pp.contains("]")) { 922 if (!pp.endsWith("[x]") || (pp.substring(0, pp.length()-3).contains("[") || (pp.substring(0, pp.length()-3).contains("]")))) { 923 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTERS_, p, url)); 924 } 925 } 926 } 927 } 928 } 929 } 930 931 932 private boolean isCompatibleType(String base, StructureDefinition sdt) { 933 StructureDefinition sdb = context.fetchTypeDefinition(base); 934 if (sdb.getType().equals(sdt.getType())) { 935 return true; 936 } 937 StructureDefinition sd = context.fetchTypeDefinition(sdt.getType()); 938 while (sd != null) { 939 if (sd.getType().equals(sdb.getType())) { 940 return true; 941 } 942 if (sd.getUrl().equals(sdb.getUrl())) { 943 return true; 944 } 945 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 946 } 947 return false; 948 } 949 950 951 private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) { 952 StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent(); 953 for (ElementDefinition sed : source.getElement()) { 954 ElementDefinition ted = sed.copy(); 955 diff.getElement().add(ted); 956 ted.setUserData("diff-source", sed); 957 } 958 return diff; 959 } 960 961 private StructureDefinitionSnapshotComponent cloneSnapshot(StructureDefinitionSnapshotComponent source, String baseType, String derivedType) { 962 StructureDefinitionSnapshotComponent diff = new StructureDefinitionSnapshotComponent(); 963 for (ElementDefinition sed : source.getElement()) { 964 ElementDefinition ted = sed.copy(); 965 ted.setId(ted.getId().replaceFirst(baseType,derivedType)); 966 ted.setPath(ted.getPath().replaceFirst(baseType,derivedType)); 967 diff.getElement().add(ted); 968 } 969 return diff; 970 } 971 972 private String constraintSummary(ElementDefinition ed) { 973 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 974 if (ed.hasPattern()) 975 b.append("pattern="+ed.getPattern().fhirType()); 976 if (ed.hasFixed()) 977 b.append("fixed="+ed.getFixed().fhirType()); 978 if (ed.hasConstraint()) 979 b.append("constraints="+ed.getConstraint().size()); 980 return b.toString(); 981 } 982 983 984 private String sliceSummary(ElementDefinition ed) { 985 if (!ed.hasSlicing() && !ed.hasSliceName()) 986 return ""; 987 if (ed.hasSliceName()) 988 return " (slicename = "+ed.getSliceName()+")"; 989 990 StringBuilder b = new StringBuilder(); 991 boolean first = true; 992 for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) { 993 if (first) 994 first = false; 995 else 996 b.append("|"); 997 b.append(d.getPath()); 998 } 999 return " (slicing by "+b.toString()+")"; 1000 } 1001 1002 1003// private String typeSummary(ElementDefinition ed) { 1004// StringBuilder b = new StringBuilder(); 1005// boolean first = true; 1006// for (TypeRefComponent tr : ed.getType()) { 1007// if (first) 1008// first = false; 1009// else 1010// b.append("|"); 1011// b.append(tr.getWorkingCode()); 1012// } 1013// return b.toString(); 1014// } 1015 1016 private String typeSummaryWithProfile(ElementDefinition ed) { 1017 StringBuilder b = new StringBuilder(); 1018 boolean first = true; 1019 for (TypeRefComponent tr : ed.getType()) { 1020 if (first) 1021 first = false; 1022 else 1023 b.append("|"); 1024 b.append(tr.getWorkingCode()); 1025 if (tr.hasProfile()) { 1026 b.append("("); 1027 b.append(tr.getProfile()); 1028 b.append(")"); 1029 1030 } 1031 } 1032 return b.toString(); 1033 } 1034 1035 1036// private boolean findMatchingElement(String id, List<ElementDefinition> list) { 1037// for (ElementDefinition ed : list) { 1038// if (ed.getId().equals(id)) 1039// return true; 1040// if (id.endsWith("[x]")) { 1041// if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains(".")) 1042// return true; 1043// } 1044// } 1045// return false; 1046// } 1047 1048 protected ElementDefinition getById(List<ElementDefinition> list, String baseId) { 1049 for (ElementDefinition t : list) { 1050 if (baseId.equals(t.getId())) { 1051 return t; 1052 } 1053 } 1054 return null; 1055 } 1056 1057 protected void updateConstraintSources(ElementDefinition ed, String url) { 1058 for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { 1059 if (!c.hasSource()) { 1060 c.setSource(url); 1061 } 1062 } 1063 1064 } 1065 1066 protected Set<String> getListOfTypes(ElementDefinition e) { 1067 Set<String> result = new HashSet<>(); 1068 for (TypeRefComponent t : e.getType()) { 1069 result.add(t.getCode()); 1070 } 1071 return result; 1072 } 1073 1074 StructureDefinition getTypeForElement(StructureDefinitionDifferentialComponent differential, int diffCursor, String profileName, 1075 List<ElementDefinition> diffMatches, ElementDefinition outcome, String webUrl, Resource srcSD) { 1076 if (outcome.getType().size() == 0) { 1077 if (outcome.hasContentReference()) { 1078 throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_CONTENT_REFERENCE_IN_THIS_CONTEXT, outcome.getContentReference(), outcome.getId(), outcome.getPath())); 1079 } else { 1080 throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), profileName)); 1081 } 1082 } 1083 if (outcome.getType().size() > 1) { 1084 for (TypeRefComponent t : outcome.getType()) { 1085 if (!t.getWorkingCode().equals("Reference")) 1086 throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName)); 1087 } 1088 } 1089 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0), webUrl, srcSD); 1090 if (dt == null) 1091 throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath())); 1092 return dt; 1093 } 1094 1095 protected String sliceNames(List<ElementDefinition> diffMatches) { 1096 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1097 for (ElementDefinition ed : diffMatches) { 1098 if (ed.hasSliceName()) { 1099 b.append(ed.getSliceName()); 1100 } 1101 } 1102 return b.toString(); 1103 } 1104 1105 protected boolean isMatchingType(StructureDefinition sd, List<TypeRefComponent> types, String inner) { 1106 while (sd != null) { 1107 for (TypeRefComponent tr : types) { 1108 if (sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && sd.getType().equals(tr.getCode())) { 1109 return true; 1110 } 1111 if (inner == null && sd.getUrl().equals(tr.getCode())) { 1112 return true; 1113 } 1114 if (inner != null) { 1115 ElementDefinition ed = null; 1116 for (ElementDefinition t : sd.getSnapshot().getElement()) { 1117 if (inner.equals(t.getId())) { 1118 ed = t; 1119 } 1120 } 1121 if (ed != null) { 1122 return isMatchingType(ed.getType(), types); 1123 } 1124 } 1125 } 1126 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 1127 } 1128 return false; 1129 } 1130 1131 private boolean isMatchingType(List<TypeRefComponent> test, List<TypeRefComponent> desired) { 1132 for (TypeRefComponent t : test) { 1133 for (TypeRefComponent d : desired) { 1134 if (t.getCode().equals(d.getCode())) { 1135 return true; 1136 } 1137 } 1138 } 1139 return false; 1140 } 1141 1142 protected boolean isValidType(TypeRefComponent t, ElementDefinition base) { 1143 for (TypeRefComponent tr : base.getType()) { 1144 if (tr.getCode().equals(t.getCode())) { 1145 return true; 1146 } 1147 if (tr.getWorkingCode().equals(t.getCode())) { 1148 System.out.println("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath()); 1149 return true; 1150 } 1151 } 1152 return false; 1153 } 1154 1155 protected boolean isGenerating(StructureDefinition sd) { 1156 return sd.hasUserData("profileutils.snapshot.generating"); 1157 } 1158 1159 1160 protected void checkNotGenerating(StructureDefinition sd, String role) { 1161 if (sd.hasUserData("profileutils.snapshot.generating")) { 1162 throw new FHIRException(context.formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, sd.getUrl(), role)); 1163 } 1164 } 1165 1166 protected boolean isBaseResource(List<TypeRefComponent> types) { 1167 if (types.isEmpty()) 1168 return false; 1169 for (TypeRefComponent type : types) { 1170 String t = type.getWorkingCode(); 1171 if ("Resource".equals(t)) 1172 return false; 1173 } 1174 return true; 1175 1176 } 1177 1178 String determineFixedType(List<ElementDefinition> diffMatches, String fixedType, int i) { 1179 if (diffMatches.get(i).getType().size() == 0 && diffMatches.get(i).hasSliceName()) { 1180 String n = tail(diffMatches.get(i).getPath()).replace("[x]", ""); 1181 String t = diffMatches.get(i).getSliceName().substring(n.length()); 1182 if (isDataType(t)) { 1183 fixedType = t; 1184 } else if (isPrimitive(Utilities.uncapitalize(t))) { 1185 fixedType = Utilities.uncapitalize(t); 1186 } else { 1187 throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__10_AND_IMPLICIT_SLICE_NAME_DOES_NOT_CONTAIN_A_VALID_TYPE__AT_, t, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName())); 1188 } 1189 } else if (diffMatches.get(i).getType().size() == 1) { 1190 fixedType = diffMatches.get(i).getType().get(0).getCode(); 1191 } else { 1192 throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__1_AT_, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName())); 1193 } 1194 return fixedType; 1195 } 1196 1197 1198 protected BaseTypeSlice chooseMatchingBaseSlice(List<BaseTypeSlice> baseSlices, String type) { 1199 for (BaseTypeSlice bs : baseSlices) { 1200 if (bs.getType().equals(type)) { 1201 return bs; 1202 } 1203 } 1204 return null; 1205 } 1206 1207 1208 protected List<BaseTypeSlice> findBaseSlices(StructureDefinitionSnapshotComponent list, int start) { 1209 List<BaseTypeSlice> res = new ArrayList<>(); 1210 ElementDefinition base = list.getElement().get(start); 1211 int i = start + 1; 1212 while (i < list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) { 1213 i++; 1214 }; 1215 while (i < list.getElement().size() && list.getElement().get(i).getPath().equals(base.getPath()) && list.getElement().get(i).hasSliceName()) { 1216 int s = i; 1217 i++; 1218 while (i < list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) { 1219 i++; 1220 }; 1221 res.add(new BaseTypeSlice(list.getElement().get(s), list.getElement().get(s).getTypeFirstRep().getCode(), s, i-1)); 1222 } 1223 return res; 1224 } 1225 1226 1227 protected String getWebUrl(StructureDefinition dt, String webUrl) { 1228 if (dt.hasUserData("path")) { 1229 // this is a hack, but it works for now, since we don't have deep folders 1230 String url = dt.getUserString("path"); 1231 int i = url.lastIndexOf("/"); 1232 if (i < 1) { 1233 return defWebRoot; 1234 } else { 1235 return url.substring(0, i+1); 1236 } 1237 } else { 1238 return webUrl; 1239 } 1240 } 1241 1242 protected void removeStatusExtensions(ElementDefinition outcome) { 1243 outcome.removeExtension(ToolingExtensions.EXT_FMM_LEVEL); 1244 outcome.removeExtension(ToolingExtensions.EXT_FMM_SUPPORT); 1245 outcome.removeExtension(ToolingExtensions.EXT_FMM_DERIVED); 1246 outcome.removeExtension(ToolingExtensions.EXT_STANDARDS_STATUS); 1247 outcome.removeExtension(ToolingExtensions.EXT_NORMATIVE_VERSION); 1248 outcome.removeExtension(ToolingExtensions.EXT_WORKGROUP); 1249 outcome.removeExtension(ToolingExtensions.EXT_FMM_SUPPORT); 1250 outcome.removeExtension(ToolingExtensions.EXT_FMM_DERIVED); 1251 } 1252 1253 protected String descED(List<ElementDefinition> list, int index) { 1254 return index >=0 && index < list.size() ? list.get(index).present() : "X"; 1255 } 1256 1257 1258 1259 protected String rootName(String cpath) { 1260 String t = tail(cpath); 1261 return t.replace("[x]", ""); 1262 } 1263 1264 1265 protected String determineTypeSlicePath(String path, String cpath) { 1266 String headP = path.substring(0, path.lastIndexOf(".")); 1267// String tailP = path.substring(path.lastIndexOf(".")+1); 1268 String tailC = cpath.substring(cpath.lastIndexOf(".")+1); 1269 return headP+"."+tailC; 1270 } 1271 1272 1273 protected boolean isImplicitSlicing(ElementDefinition ed, String path) { 1274 if (ed == null || ed.getPath() == null || path == null) 1275 return false; 1276 if (path.equals(ed.getPath())) 1277 return false; 1278 boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3)); 1279 return ok; 1280 } 1281 1282 1283 protected boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) { 1284// if (diffMatches.size() < 2) 1285 // return false; 1286 String p = diffMatches.get(0).getPath(); 1287 if (!p.endsWith("[x]") && !cPath.endsWith("[x]")) 1288 return false; 1289 typeList.clear(); 1290 String rn = tail(cPath); 1291 rn = rn.substring(0, rn.length()-3); 1292 for (int i = 0; i < diffMatches.size(); i++) { 1293 ElementDefinition ed = diffMatches.get(i); 1294 String n = tail(ed.getPath()); 1295 if (!n.startsWith(rn)) 1296 return false; 1297 String s = n.substring(rn.length()); 1298 if (!s.contains(".")) { 1299 if (ed.hasSliceName() && ed.getType().size() == 1) { 1300 typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode())); 1301 } else if (ed.hasSliceName() && ed.getType().size() == 0) { 1302 if (isDataType(s)) { 1303 typeList.add(new TypeSlice(ed, s)); 1304 } else if (isPrimitive(Utilities.uncapitalize(s))) { 1305 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 1306 } else { 1307 String tn = ed.getSliceName().substring(n.length()); 1308 if (isDataType(tn)) { 1309 typeList.add(new TypeSlice(ed, tn)); 1310 } else if (isPrimitive(Utilities.uncapitalize(tn))) { 1311 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(tn))); 1312 } 1313 } 1314 } else if (!ed.hasSliceName() && !s.equals("[x]")) { 1315 if (isDataType(s)) 1316 typeList.add(new TypeSlice(ed, s)); 1317 else if (isConstrainedDataType(s)) 1318 typeList.add(new TypeSlice(ed, baseType(s))); 1319 else if (isPrimitive(Utilities.uncapitalize(s))) 1320 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 1321 } else if (!ed.hasSliceName() && s.equals("[x]")) 1322 typeList.add(new TypeSlice(ed, null)); 1323 } 1324 } 1325 return true; 1326 } 1327 1328 1329 protected List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, String path) { 1330 List<ElementRedirection> result = new ArrayList<ElementRedirection>(); 1331 result.addAll(redirector); 1332 result.add(new ElementRedirection(outcome, path)); 1333 return result; 1334 } 1335 1336 1337 protected List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) { 1338 List<TypeRefComponent> res = new ArrayList<TypeRefComponent>(); 1339 for (TypeRefComponent tr : type) { 1340 if (t.equals(tr.getWorkingCode())) 1341 res.add(tr); 1342 } 1343 return res; 1344 } 1345 1346 1347 protected void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) { 1348 outcome.setContentReference(null); 1349 outcome.getType().clear(); // though it should be clear anyway 1350 outcome.getType().addAll(tgt.getType()); 1351 } 1352 1353 1354 protected boolean baseWalksInto(List<ElementDefinition> elements, int cursor) { 1355 if (cursor >= elements.size()) 1356 return false; 1357 String path = elements.get(cursor).getPath(); 1358 String prevPath = elements.get(cursor - 1).getPath(); 1359 return path.startsWith(prevPath + "."); 1360 } 1361 1362 1363 protected ElementDefinition fillOutFromBase(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError { 1364 ElementDefinition res = profile.copy(); 1365 if (!res.hasSliceName()) 1366 res.setSliceName(usage.getSliceName()); 1367 if (!res.hasLabel()) 1368 res.setLabel(usage.getLabel()); 1369 for (Coding c : usage.getCode()) 1370 if (!res.hasCode(c)) 1371 res.addCode(c); 1372 1373 if (!res.hasDefinition()) 1374 res.setDefinition(usage.getDefinition()); 1375 if (!res.hasShort() && usage.hasShort()) 1376 res.setShort(usage.getShort()); 1377 if (!res.hasComment() && usage.hasComment()) 1378 res.setComment(usage.getComment()); 1379 if (!res.hasRequirements() && usage.hasRequirements()) 1380 res.setRequirements(usage.getRequirements()); 1381 for (StringType c : usage.getAlias()) 1382 if (!res.hasAlias(c.getValue())) 1383 res.addAlias(c.getValue()); 1384 if (!res.hasMin() && usage.hasMin()) 1385 res.setMin(usage.getMin()); 1386 if (!res.hasMax() && usage.hasMax()) 1387 res.setMax(usage.getMax()); 1388 1389 if (!res.hasFixed() && usage.hasFixed()) 1390 res.setFixed(usage.getFixed()); 1391 if (!res.hasPattern() && usage.hasPattern()) 1392 res.setPattern(usage.getPattern()); 1393 if (!res.hasExample() && usage.hasExample()) 1394 res.setExample(usage.getExample()); 1395 if (!res.hasMinValue() && usage.hasMinValue()) 1396 res.setMinValue(usage.getMinValue()); 1397 if (!res.hasMaxValue() && usage.hasMaxValue()) 1398 res.setMaxValue(usage.getMaxValue()); 1399 if (!res.hasMaxLength() && usage.hasMaxLength()) 1400 res.setMaxLength(usage.getMaxLength()); 1401 if (!res.hasMustSupport() && usage.hasMustSupport()) 1402 res.setMustSupport(usage.getMustSupport()); 1403 if (!res.hasMustHaveValue() && usage.hasMustHaveValue()) 1404 res.setMustHaveValue(usage.getMustHaveValue()); 1405 if (!res.hasBinding() && usage.hasBinding()) 1406 res.setBinding(usage.getBinding().copy()); 1407 for (ElementDefinitionConstraintComponent c : usage.getConstraint()) 1408 if (!res.hasConstraint(c.getKey())) 1409 res.addConstraint(c); 1410 for (Extension e : usage.getExtension()) { 1411 if (!res.hasExtension(e.getUrl())) 1412 res.addExtension(e.copy()); 1413 } 1414 1415 return res; 1416 } 1417 1418 1419 protected boolean checkExtensionDoco(ElementDefinition base) { 1420 // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff 1421 boolean isExtension = (base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension")) && 1422 (!base.hasBase() || !"II.extension".equals(base.getBase().getPath())); 1423 if (isExtension) { 1424 base.setDefinition("An Extension"); 1425 base.setShort("Extension"); 1426 base.setCommentElement(null); 1427 base.setRequirementsElement(null); 1428 base.getAlias().clear(); 1429 base.getMapping().clear(); 1430 } 1431 return isExtension; 1432 } 1433 1434 1435 protected String pathTail(List<ElementDefinition> diffMatches, int i) { 1436 1437 ElementDefinition d = diffMatches.get(i); 1438 String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath(); 1439 return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : ""); 1440 } 1441 1442 1443 protected void markDerived(ElementDefinition outcome) { 1444 for (ElementDefinitionConstraintComponent inv : outcome.getConstraint()) 1445 inv.setUserData(UD_IS_DERIVED, true); 1446 } 1447 1448 1449 static String summarizeSlicing(ElementDefinitionSlicingComponent slice) { 1450 StringBuilder b = new StringBuilder(); 1451 boolean first = true; 1452 for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) { 1453 if (first) 1454 first = false; 1455 else 1456 b.append(", "); 1457 b.append(d.getType().toCode()+":"+d.getPath()); 1458 } 1459 b.append(" ("); 1460 if (slice.hasOrdered()) 1461 b.append(slice.getOrdered() ? "ordered" : "unordered"); 1462 b.append("/"); 1463 if (slice.hasRules()) 1464 b.append(slice.getRules().toCode()); 1465 b.append(")"); 1466 if (slice.hasDescription()) { 1467 b.append(" \""); 1468 b.append(slice.getDescription()); 1469 b.append("\""); 1470 } 1471 return b.toString(); 1472 } 1473 1474 1475 protected void updateFromBase(ElementDefinition derived, ElementDefinition base, String baseProfileUrl) { 1476 derived.setUserData(UD_BASE_MODEL, baseProfileUrl); 1477 derived.setUserData(UD_BASE_PATH, base.getPath()); 1478 if (base.hasBase()) { 1479 if (!derived.hasBase()) 1480 derived.setBase(new ElementDefinitionBaseComponent()); 1481 derived.getBase().setPath(base.getBase().getPath()); 1482 derived.getBase().setMin(base.getBase().getMin()); 1483 derived.getBase().setMax(base.getBase().getMax()); 1484 } else { 1485 if (!derived.hasBase()) 1486 derived.setBase(new ElementDefinitionBaseComponent()); 1487 derived.getBase().setPath(base.getPath()); 1488 derived.getBase().setMin(base.getMin()); 1489 derived.getBase().setMax(base.getMax()); 1490 } 1491 } 1492 1493 1494 protected boolean pathStartsWith(String p1, String p2) { 1495 return p1.startsWith(p2) || (p2.endsWith("[x].") && p1.startsWith(p2.substring(0, p2.length()-4))); 1496 } 1497 1498 private boolean pathMatches(String p1, String p2) { 1499 return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains(".")); 1500 } 1501 1502 1503 protected String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) { 1504 if (contextPath == null) 1505 return pathSimple; 1506// String ptail = pathSimple.substring(contextPath.length() + 1); 1507 if (redirector != null && redirector.size() > 0) { 1508 String ptail = null; 1509 if (contextPath.length() >= pathSimple.length()) { 1510 ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1511 } else { 1512 ptail = pathSimple.substring(contextPath.length()+1); 1513 } 1514 return redirector.get(redirector.size()-1).getPath()+"."+ptail; 1515// return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1); 1516 } else { 1517 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1518 return contextPath+"."+ptail; 1519 } 1520 } 1521 1522 protected String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, String redirectSource) { 1523 String s; 1524 if (contextPath == null) 1525 s = pathSimple; 1526 else { 1527 if (redirector != null && redirector.size() > 0) { 1528 String ptail = null; 1529 if (redirectSource.length() >= pathSimple.length()) { 1530 ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1531 } else { 1532 ptail = pathSimple.substring(redirectSource.length()+1); 1533 } 1534 // ptail = ptail.substring(ptail.indexOf(".")+1); 1535 s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail; 1536 } else { 1537 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1538 s = contextPath+"."+ptail; 1539 } 1540 } 1541 return s; 1542 } 1543 1544 protected StructureDefinition getProfileForDataType(TypeRefComponent type, String webUrl, Resource src) { 1545 StructureDefinition sd = null; 1546 if (type.hasProfile()) { 1547 sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue(), src); 1548 if (sd == null) { 1549 if (xver != null && xver.matchingUrl(type.getProfile().get(0).getValue()) && xver.status(type.getProfile().get(0).getValue()) == XVerExtensionStatus.Valid) { 1550 sd = xver.makeDefinition(type.getProfile().get(0).getValue()); 1551 generateSnapshot(context.fetchTypeDefinition("Extension"), sd, sd.getUrl(), webUrl, sd.getName()); 1552 } 1553 } 1554 if (sd == null) { 1555 if (debug) { 1556 System.out.println("Failed to find referenced profile: " + type.getProfile()); 1557 } 1558 } 1559 1560 } 1561 if (sd == null) 1562 sd = context.fetchTypeDefinition(type.getWorkingCode()); 1563 if (sd == null) 1564 System.out.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM 1565 return sd; 1566 } 1567 1568 protected StructureDefinition getProfileForDataType(String type) { 1569 StructureDefinition sd = context.fetchTypeDefinition(type); 1570 if (sd == null) 1571 System.out.println("XX: failed to find profle for type: " + type); // debug GJM 1572 return sd; 1573 } 1574 1575 static String typeCode(List<TypeRefComponent> types) { 1576 StringBuilder b = new StringBuilder(); 1577 boolean first = true; 1578 for (TypeRefComponent type : types) { 1579 if (first) first = false; else b.append(", "); 1580 b.append(type.getWorkingCode()); 1581 if (type.hasTargetProfile()) 1582 b.append("{"+type.getTargetProfile()+"}"); 1583 else if (type.hasProfile()) 1584 b.append("{"+type.getProfile()+"}"); 1585 } 1586 return b.toString(); 1587 } 1588 1589 1590 protected boolean isDataType(List<TypeRefComponent> types) { 1591 if (types.isEmpty()) 1592 return false; 1593 for (TypeRefComponent type : types) { 1594 String t = type.getWorkingCode(); 1595 if (!isDataType(t) && !isPrimitive(t)) 1596 return false; 1597 } 1598 return true; 1599 } 1600 1601 1602 /** 1603 * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url 1604 * @param url - the base url to use to turn internal references into absolute references 1605 * @param element - the Element to update 1606 * @return - the updated Element 1607 */ 1608 public ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) { 1609 if (element != null) { 1610 ElementDefinition defn = element; 1611 if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#")) 1612 defn.getBinding().setValueSet(url+defn.getBinding().getValueSet()); 1613 for (TypeRefComponent t : defn.getType()) { 1614 for (UriType u : t.getProfile()) { 1615 if (u.getValue().startsWith("#")) 1616 u.setValue(url+t.getProfile()); 1617 } 1618 for (UriType u : t.getTargetProfile()) { 1619 if (u.getValue().startsWith("#")) 1620 u.setValue(url+t.getTargetProfile()); 1621 } 1622 } 1623 if (webUrl != null) { 1624 // also, must touch up the markdown 1625 if (element.hasDefinition()) 1626 element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, null, false)); 1627 if (element.hasComment()) 1628 element.setComment(processRelativeUrls(element.getComment(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, null, false)); 1629 if (element.hasRequirements()) 1630 element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, null, false)); 1631 if (element.hasMeaningWhenMissing()) 1632 element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, null, false)); 1633 } 1634 } 1635 return element; 1636 } 1637 1638 public static String processRelativeUrls(String markdown, String webUrl, String basePath, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames, boolean processRelatives) { 1639 if (markdown == null) { 1640 return ""; 1641 } 1642 StringBuilder b = new StringBuilder(); 1643 int i = 0; 1644 while (i < markdown.length()) { 1645 if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { 1646 int j = i + 2; 1647 while (j < markdown.length() && markdown.charAt(j) != ')') 1648 j++; 1649 if (j < markdown.length()) { 1650 String url = markdown.substring(i+2, j); 1651 if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) { 1652 // 1653 // In principle, relative URLs are supposed to be converted to absolute URLs in snapshots. 1654 // that's what this code is doing. 1655 // 1656 // But that hasn't always happened and there's packages out there where the snapshots 1657 // contain relative references that actually are references to the main specification 1658 // 1659 // This code is trying to guess which relative references are actually to the 1660 // base specification. 1661 // 1662 if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames)) { 1663 b.append("]("); 1664 b.append(basePath); 1665 i = i + 1; 1666 } else { 1667 b.append("]("); 1668 // disabled 7-Dec 2021 GDG - we don't want to fool with relative URLs at all? 1669 // re-enabled 11-Feb 2022 GDG - we do want to do this. At least, $assemble in davinci-dtr, where the markdown comes from the SDC IG, and an SDC local reference must be changed to point to SDC. in this case, it's called when generating snapshots 1670 // added processRelatives parameter to deal with this (well, to try) 1671 if (processRelatives && webUrl != null && !issLocalFileName(url, localFilenames)) { 1672// System.out.println("Making "+url+" relative to '"+webUrl+"'"); 1673 b.append(webUrl); 1674 } else { 1675// System.out.println("Not making "+url+" relative to '"+webUrl+"'"); 1676 } 1677 i = i + 1; 1678 } 1679 } else 1680 b.append(markdown.charAt(i)); 1681 } else 1682 b.append(markdown.charAt(i)); 1683 } else { 1684 b.append(markdown.charAt(i)); 1685 } 1686 i++; 1687 } 1688 return b.toString(); 1689 } 1690 1691 private static boolean issLocalFileName(String url, Set<String> localFilenames) { 1692 if (localFilenames != null) { 1693 for (String n : localFilenames) { 1694 if (url.startsWith(n.toLowerCase())) { 1695 return true; 1696 } 1697 } 1698 } 1699 return false; 1700 } 1701 1702 1703 private static boolean isLikelySourceURLReference(String url, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames) { 1704 if (resourceNames != null) { 1705 for (String n : resourceNames) { 1706 if (n != null && url.startsWith(n.toLowerCase()+".html")) { 1707 return true; 1708 } 1709 if (n != null && url.startsWith(n.toLowerCase()+"-definitions.html")) { 1710 return true; 1711 } 1712 } 1713 } 1714 if (localFilenames != null) { 1715 for (String n : localFilenames) { 1716 if (n != null && url.startsWith(n.toLowerCase())) { 1717 return false; 1718 } 1719 } 1720 } 1721 if (baseFilenames != null) { 1722 for (String n : baseFilenames) { 1723 if (n != null && url.startsWith(n.toLowerCase())) { 1724 return true; 1725 } 1726 } 1727 } 1728 return 1729 url.startsWith("extensibility.html") || 1730 url.startsWith("terminologies.html") || 1731 url.startsWith("observation.html") || 1732 url.startsWith("codesystem.html") || 1733 url.startsWith("fhirpath.html") || 1734 url.startsWith("datatypes.html") || 1735 url.startsWith("operations.html") || 1736 url.startsWith("resource.html") || 1737 url.startsWith("elementdefinition.html") || 1738 url.startsWith("element-definitions.html") || 1739 url.startsWith("snomedct.html") || 1740 url.startsWith("loinc.html") || 1741 url.startsWith("http.html") || 1742 url.startsWith("references") || 1743 url.startsWith("narrative.html") || 1744 url.startsWith("search.html") || 1745 url.startsWith("patient-operation-match.html") || 1746 (url.startsWith("extension-") && url.contains(".html")) || 1747 url.startsWith("resource-definitions.html"); 1748 } 1749 1750 protected List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) { 1751 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1752 String path = current.getPath(); 1753 int cursor = list.indexOf(current)+1; 1754 while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) { 1755 if (pathMatches(list.get(cursor).getPath(), path)) 1756 result.add(list.get(cursor)); 1757 cursor++; 1758 } 1759 return result; 1760 } 1761 1762 protected void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) { 1763 if (src.hasOrderedElement()) 1764 dst.setOrderedElement(src.getOrderedElement().copy()); 1765 if (src.hasDiscriminator()) { 1766 // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll because it uses object equality, not string equality 1767 for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) { 1768 boolean found = false; 1769 for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) { 1770 if (matches(d, s)) { 1771 found = true; 1772 break; 1773 } 1774 } 1775 if (!found) 1776 dst.getDiscriminator().add(s); 1777 } 1778 } 1779 if (src.hasRulesElement()) 1780 dst.setRulesElement(src.getRulesElement().copy()); 1781 } 1782 1783 protected boolean orderMatches(BooleanType diff, BooleanType base) { 1784 return (diff == null) || (base == null) || (diff.getValue() == base.getValue()); 1785 } 1786 1787 protected boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) { 1788 if (diff.isEmpty() || base.isEmpty()) 1789 return true; 1790 if (diff.size() != base.size()) 1791 return false; 1792 for (int i = 0; i < diff.size(); i++) 1793 if (!matches(diff.get(i), base.get(i))) 1794 return false; 1795 return true; 1796 } 1797 1798 private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) { 1799 return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath()); 1800 } 1801 1802 1803 protected boolean ruleMatches(SlicingRules diff, SlicingRules base) { 1804 return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) || 1805 ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED)); 1806 } 1807 1808 protected boolean isSlicedToOneOnly(ElementDefinition e) { 1809 return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); 1810 } 1811 1812 protected ElementDefinitionSlicingComponent makeExtensionSlicing() { 1813 ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); 1814 slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); 1815 slice.setOrdered(false); 1816 slice.setRules(SlicingRules.OPEN); 1817 return slice; 1818 } 1819 1820 protected boolean isExtension(ElementDefinition currentBase) { 1821 return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension"); 1822 } 1823 1824 boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException { 1825 end = Math.min(context.getElement().size(), end); 1826 start = Math.max(0, start); 1827 1828 for (int i = start; i <= end; i++) { 1829 ElementDefinition ed = context.getElement().get(i); 1830 String statedPath = ed.getPath(); 1831 if (!allowSlices && statedPath.equals(path) && ed.hasSliceName()) { 1832 return false; 1833 } else if (statedPath.startsWith(path+".")) { 1834 return true; 1835 } else if (path.endsWith("[x]") && statedPath.startsWith(path.substring(0, path.length() -3))) { 1836 return true; 1837 } else if (i != start && !allowSlices && !statedPath.startsWith(path+".")) { 1838 return false; 1839 } else if (i != start && allowSlices && !statedPath.startsWith(path)) { 1840 return false; 1841 } else { 1842 // not sure why we get here, but returning false at this point makes a bunch of tests fail 1843 } 1844 } 1845 return false; 1846 } 1847 1848 protected List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException { 1849 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1850 String[] p = path.split("\\."); 1851 for (int i = start; i <= end; i++) { 1852 String statedPath = context.getElement().get(i).getPath(); 1853 String[] sp = statedPath.split("\\."); 1854 boolean ok = sp.length == p.length; 1855 for (int j = 0; j < p.length; j++) { 1856 ok = ok && sp.length > j && (p[j].equals(sp[j]) || isSameBase(p[j], sp[j])); 1857 } 1858// don't need this debug check - everything is ok 1859// if (ok != (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 && 1860// statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) && 1861// (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains("."))))) { 1862// System.out.println("mismatch in paths: "+statedPath +" vs " +path); 1863// } 1864 if (ok) { 1865 /* 1866 * Commenting this out because it raises warnings when profiling inherited elements. For example, 1867 * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry') 1868 * Not sure we have enough information here to do the check properly. Might be better done when we're sorting the profile? 1869 1870 if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath())) 1871 messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.VALUE, "StructureDefinition.differential.element["+Integer.toString(start)+"]", "Error: unknown element '"+context.getElement().get(start).getPath()+"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')", IssueSeverity.WARNING)); 1872 1873 */ 1874 result.add(context.getElement().get(i)); 1875 } 1876 } 1877 return result; 1878 } 1879 1880 1881 private boolean isSameBase(String p, String sp) { 1882 return (p.endsWith("[x]") && sp.startsWith(p.substring(0, p.length()-3))) || (sp.endsWith("[x]") && p.startsWith(sp.substring(0, sp.length()-3))) ; 1883 } 1884 1885 protected int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) { 1886 int result = cursor; 1887 if (cursor >= context.getElement().size()) 1888 return result; 1889 String path = context.getElement().get(cursor).getPath()+"."; 1890 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 1891 result++; 1892 return result; 1893 } 1894 1895 protected int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) { 1896 int result = cursor; 1897 String path = context.getElement().get(cursor).getPath()+"."; 1898 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 1899 result++; 1900 return result; 1901 } 1902 1903 protected boolean unbounded(ElementDefinition definition) { 1904 StringType max = definition.getMaxElement(); 1905 if (max == null) 1906 return false; // this is not valid 1907 if (max.getValue().equals("1")) 1908 return false; 1909 if (max.getValue().equals("0")) 1910 return false; 1911 return true; 1912 } 1913 1914 protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc) throws DefinitionException, FHIRException { 1915 source.setUserData(UD_GENERATED_IN_SNAPSHOT, dest); 1916 // we start with a clone of the base profile ('dest') and we copy from the profile ('source') 1917 // over the top for anything the source has 1918 ElementDefinition base = dest; 1919 ElementDefinition derived = source; 1920 derived.setUserData(UD_DERIVATION_POINTER, base); 1921 boolean isExtension = checkExtensionDoco(base); 1922 1923 1924 for (Extension ext : source.getExtension()) { 1925 if (Utilities.existsInList(ext.getUrl(), INHERITED_ED_URLS) && !dest.hasExtension(ext.getUrl())) { 1926 dest.getExtension().add(ext.copy()); 1927 } 1928 } 1929 // Before applying changes, apply them to what's in the profile 1930 StructureDefinition profile = null; 1931 if (base.hasSliceName()) 1932 profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null; 1933 if (profile==null) 1934 profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue(), derivedSrc) : null; 1935 if (profile != null) { 1936 ElementDefinition e = profile.getSnapshot().getElement().get(0); 1937 String webroot = profile.getUserString("webroot"); 1938 1939 if (e.hasDefinition()) { 1940 base.setDefinition(processRelativeUrls(e.getDefinition(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, null, true)); 1941 } 1942 base.setShort(e.getShort()); 1943 if (e.hasCommentElement()) 1944 base.setCommentElement(e.getCommentElement()); 1945 if (e.hasRequirementsElement()) 1946 base.setRequirementsElement(e.getRequirementsElement()); 1947 base.getAlias().clear(); 1948 base.getAlias().addAll(e.getAlias()); 1949 base.getMapping().clear(); 1950 base.getMapping().addAll(e.getMapping()); 1951 } 1952 if (derived != null) { 1953 if (derived.hasSliceName()) { 1954 base.setSliceName(derived.getSliceName()); 1955 } 1956 1957 if (derived.hasShortElement()) { 1958 if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false)) 1959 base.setShortElement(derived.getShortElement().copy()); 1960 else if (trimDifferential) 1961 derived.setShortElement(null); 1962 else if (derived.hasShortElement()) 1963 derived.getShortElement().setUserData(UD_DERIVATION_EQUALS, true); 1964 } 1965 1966 if (derived.hasDefinitionElement()) { 1967 if (derived.getDefinition().startsWith("...")) 1968 base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition())); 1969 else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) 1970 base.setDefinitionElement(derived.getDefinitionElement().copy()); 1971 else if (trimDifferential) 1972 derived.setDefinitionElement(null); 1973 else if (derived.hasDefinitionElement()) 1974 derived.getDefinitionElement().setUserData(UD_DERIVATION_EQUALS, true); 1975 } 1976 1977 if (derived.hasCommentElement()) { 1978 if (derived.getComment().startsWith("...")) 1979 base.setComment(Utilities.appendDerivedTextToBase(base.getComment(), derived.getComment())); 1980 else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false)) 1981 base.setCommentElement(derived.getCommentElement().copy()); 1982 else if (trimDifferential) 1983 base.setCommentElement(derived.getCommentElement().copy()); 1984 else if (derived.hasCommentElement()) 1985 derived.getCommentElement().setUserData(UD_DERIVATION_EQUALS, true); 1986 } 1987 1988 if (derived.hasLabelElement()) { 1989 if (derived.getLabel().startsWith("...")) 1990 base.setLabel(Utilities.appendDerivedTextToBase(base.getLabel(), derived.getLabel())); 1991 else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false)) 1992 base.setLabelElement(derived.getLabelElement().copy()); 1993 else if (trimDifferential) 1994 base.setLabelElement(derived.getLabelElement().copy()); 1995 else if (derived.hasLabelElement()) 1996 derived.getLabelElement().setUserData(UD_DERIVATION_EQUALS, true); 1997 } 1998 1999 if (derived.hasRequirementsElement()) { 2000 if (derived.getRequirements().startsWith("...")) 2001 base.setRequirements(Utilities.appendDerivedTextToBase(base.getRequirements(), derived.getRequirements())); 2002 else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false)) 2003 base.setRequirementsElement(derived.getRequirementsElement().copy()); 2004 else if (trimDifferential) 2005 base.setRequirementsElement(derived.getRequirementsElement().copy()); 2006 else if (derived.hasRequirementsElement()) 2007 derived.getRequirementsElement().setUserData(UD_DERIVATION_EQUALS, true); 2008 } 2009 // sdf-9 2010 if (derived.hasRequirements() && !base.getPath().contains(".")) 2011 derived.setRequirements(null); 2012 if (base.hasRequirements() && !base.getPath().contains(".")) 2013 base.setRequirements(null); 2014 2015 if (derived.hasAlias()) { 2016 if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false)) 2017 for (StringType s : derived.getAlias()) { 2018 if (!base.hasAlias(s.getValue())) 2019 base.getAlias().add(s.copy()); 2020 } 2021 else if (trimDifferential) 2022 derived.getAlias().clear(); 2023 else 2024 for (StringType t : derived.getAlias()) 2025 t.setUserData(UD_DERIVATION_EQUALS, true); 2026 } 2027 2028 if (derived.hasMinElement()) { 2029 if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { 2030 if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply 2031 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", ValidationMessage.IssueSeverity.ERROR)); 2032 base.setMinElement(derived.getMinElement().copy()); 2033 } else if (trimDifferential) 2034 derived.setMinElement(null); 2035 else 2036 derived.getMinElement().setUserData(UD_DERIVATION_EQUALS, true); 2037 } 2038 2039 if (derived.hasMaxElement()) { 2040 if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { 2041 if (isLargerMax(derived.getMax(), base.getMax())) 2042 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR)); 2043 base.setMaxElement(derived.getMaxElement().copy()); 2044 } else if (trimDifferential) 2045 derived.setMaxElement(null); 2046 else 2047 derived.getMaxElement().setUserData(UD_DERIVATION_EQUALS, true); 2048 } 2049 2050 if (derived.hasFixed()) { 2051 if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) { 2052 base.setFixed(derived.getFixed().copy()); 2053 } else if (trimDifferential) 2054 derived.setFixed(null); 2055 else 2056 derived.getFixed().setUserData(UD_DERIVATION_EQUALS, true); 2057 } 2058 2059 if (derived.hasPattern()) { 2060 if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) { 2061 base.setPattern(derived.getPattern().copy()); 2062 } else 2063 if (trimDifferential) 2064 derived.setPattern(null); 2065 else 2066 derived.getPattern().setUserData(UD_DERIVATION_EQUALS, true); 2067 } 2068 2069 for (ElementDefinitionExampleComponent ex : derived.getExample()) { 2070 boolean found = false; 2071 for (ElementDefinitionExampleComponent exS : base.getExample()) 2072 if (Base.compareDeep(ex, exS, false)) 2073 found = true; 2074 if (!found) 2075 base.addExample(ex.copy()); 2076 else if (trimDifferential) 2077 derived.getExample().remove(ex); 2078 else 2079 ex.setUserData(UD_DERIVATION_EQUALS, true); 2080 } 2081 2082 if (derived.hasMaxLengthElement()) { 2083 if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false)) 2084 base.setMaxLengthElement(derived.getMaxLengthElement().copy()); 2085 else if (trimDifferential) 2086 derived.setMaxLengthElement(null); 2087 else 2088 derived.getMaxLengthElement().setUserData(UD_DERIVATION_EQUALS, true); 2089 } 2090 2091 if (derived.hasMaxValue()) { 2092 if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false)) 2093 base.setMaxValue(derived.getMaxValue().copy()); 2094 else if (trimDifferential) 2095 derived.setMaxValue(null); 2096 else 2097 derived.getMaxValue().setUserData(UD_DERIVATION_EQUALS, true); 2098 } 2099 2100 if (derived.hasMinValue()) { 2101 if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false)) 2102 base.setMinValue(derived.getMinValue().copy()); 2103 else if (trimDifferential) 2104 derived.setMinValue(null); 2105 else 2106 derived.getMinValue().setUserData(UD_DERIVATION_EQUALS, true); 2107 } 2108 2109 // todo: what to do about conditions? 2110 // condition : id 0..* 2111 2112 if (derived.hasMustSupportElement()) { 2113 if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))) { 2114 if (base.hasMustSupport() && base.getMustSupport() && !derived.getMustSupport()) { 2115 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-support = false] when [must-support = true] in the base profile", ValidationMessage.IssueSeverity.ERROR)); 2116 } 2117 base.setMustSupportElement(derived.getMustSupportElement().copy()); 2118 } else if (trimDifferential) 2119 derived.setMustSupportElement(null); 2120 else 2121 derived.getMustSupportElement().setUserData(UD_DERIVATION_EQUALS, true); 2122 } 2123 2124 if (derived.hasMustHaveValueElement()) { 2125 if (!(base.hasMustHaveValueElement() && Base.compareDeep(derived.getMustHaveValueElement(), base.getMustHaveValueElement(), false))) { 2126 if (base.hasMustHaveValue() && base.getMustHaveValue() && !derived.getMustHaveValue()) { 2127 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-have-value = false] when [must-have-value = true] in the base profile", ValidationMessage.IssueSeverity.ERROR)); 2128 } 2129 base.setMustHaveValueElement(derived.getMustHaveValueElement().copy()); 2130 } else if (trimDifferential) 2131 derived.setMustHaveValueElement(null); 2132 else 2133 derived.getMustHaveValueElement().setUserData(UD_DERIVATION_EQUALS, true); 2134 } 2135 2136 2137 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 2138 // but extensions can change isModifier 2139 if (isExtension) { 2140 if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) 2141 base.setIsModifierElement(derived.getIsModifierElement().copy()); 2142 else if (trimDifferential) 2143 derived.setIsModifierElement(null); 2144 else if (derived.hasIsModifierElement()) 2145 derived.getIsModifierElement().setUserData(UD_DERIVATION_EQUALS, true); 2146 if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) 2147 base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy()); 2148 else if (trimDifferential) 2149 derived.setIsModifierReasonElement(null); 2150 else if (derived.hasIsModifierReasonElement()) 2151 derived.getIsModifierReasonElement().setUserData(UD_DERIVATION_EQUALS, true); 2152 } 2153 2154 if (derived.hasBinding()) { 2155 if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 2156 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 2157 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change the binding on "+derived.getPath()+" from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), ValidationMessage.IssueSeverity.ERROR)); 2158// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 2159 else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) { 2160 ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet(), srcSD); 2161 ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet(), derivedSrc); 2162 if (baseVs == null) { 2163 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 2164 } else if (contextVs == null) { 2165 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 2166 } else { 2167 ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false); 2168 ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false); 2169 if (expBase.getValueset() == null) 2170 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 2171 else if (expDerived.getValueset() == null) 2172 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 2173 else if (ToolingExtensions.hasExtension(expBase.getValueset().getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY)) 2174 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - base value set is too large to check", ValidationMessage.IssueSeverity.WARNING)); 2175 else if (!isSubset(expBase.getValueset(), expDerived.getValueset())) 2176 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR)); 2177 } 2178 } 2179 ElementDefinitionBindingComponent d = derived.getBinding(); 2180 ElementDefinitionBindingComponent nb = base.getBinding().copy(); 2181 if (!COPY_BINDING_EXTENSIONS) { 2182 nb.getExtension().clear(); 2183 } 2184 nb.setDescription(null); 2185 nb.getExtension().addAll(d.getExtension()); 2186 if (d.hasStrength()) { 2187 nb.setStrength(d.getStrength()); 2188 } 2189 if (d.hasDescription()) { 2190 nb.setDescription(d.getDescription()); 2191 } 2192 if (d.hasValueSet()) { 2193 nb.setValueSet(d.getValueSet()); 2194 } 2195 base.setBinding(nb); 2196 } else if (trimDifferential) 2197 derived.setBinding(null); 2198 else 2199 derived.getBinding().setUserData(UD_DERIVATION_EQUALS, true); 2200 } // else if (base.hasBinding() && doesn't have bindable type ) 2201 // base 2202 2203 if (derived.hasIsSummaryElement()) { 2204 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) { 2205 if (base.hasIsSummary() && !context.getVersion().equals("1.4.0")) // work around a known issue with some 1.4.0 cosntraints 2206 throw new Error(context.formatMessage(I18nConstants.ERROR_IN_PROFILE__AT__BASE_ISSUMMARY___DERIVED_ISSUMMARY__, purl, derived.getPath(), base.getIsSummaryElement().asStringValue(), derived.getIsSummaryElement().asStringValue())); 2207 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 2208 } else if (trimDifferential) 2209 derived.setIsSummaryElement(null); 2210 else 2211 derived.getIsSummaryElement().setUserData(UD_DERIVATION_EQUALS, true); 2212 } 2213 2214 if (derived.hasType()) { 2215 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 2216 if (base.hasType()) { 2217 for (TypeRefComponent ts : derived.getType()) { 2218 checkTypeDerivation(purl, derivedSrc, base, derived, ts); 2219 } 2220 } 2221 base.getType().clear(); 2222 for (TypeRefComponent t : derived.getType()) { 2223 TypeRefComponent tt = t.copy(); 2224// tt.setUserData(DERIVATION_EQUALS, true); 2225 base.getType().add(tt); 2226 } 2227 } 2228 else if (trimDifferential) 2229 derived.getType().clear(); 2230 else 2231 for (TypeRefComponent t : derived.getType()) 2232 t.setUserData(UD_DERIVATION_EQUALS, true); 2233 } 2234 2235 if (derived.hasMapping()) { 2236 // todo: mappings are not cumulative - one replaces another 2237 if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) { 2238 for (ElementDefinitionMappingComponent s : derived.getMapping()) { 2239 boolean found = false; 2240 for (ElementDefinitionMappingComponent d : base.getMapping()) { 2241 found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap())); 2242 } 2243 if (!found) { 2244 base.getMapping().add(s); 2245 } 2246 } 2247 } 2248 else if (trimDifferential) { 2249 derived.getMapping().clear(); 2250 } else { 2251 for (ElementDefinitionMappingComponent t : derived.getMapping()) { 2252 t.setUserData(UD_DERIVATION_EQUALS, true); 2253 } 2254 } 2255 } 2256 for (ElementDefinitionMappingComponent m : base.getMapping()) { 2257 if (m.hasMap()) { 2258 m.setMap(m.getMap().trim()); 2259 } 2260 } 2261 2262 // todo: constraints are cumulative. there is no replacing 2263 for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 2264 s.setUserData(UD_IS_DERIVED, true); 2265 if (!s.hasSource()) { 2266 s.setSource(srcSD.getUrl()); 2267 } 2268 } 2269 if (derived.hasConstraint()) { 2270 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 2271 if (!base.hasConstraint(s.getKey())) { 2272 ElementDefinitionConstraintComponent inv = s.copy(); 2273 base.getConstraint().add(inv); 2274 } 2275 } 2276 } 2277 for (IdType id : derived.getCondition()) { 2278 if (!base.hasCondition(id)) { 2279 base.getCondition().add(id); 2280 } 2281 } 2282 2283 // now, check that we still have a bindable type; if not, delete the binding - see task 8477 2284 if (dest.hasBinding() && !hasBindableType(dest)) { 2285 dest.setBinding(null); 2286 } 2287 2288 // finally, we copy any extensions from source to dest 2289 for (Extension ex : derived.getExtension()) { 2290 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ex.getUrl(), derivedSrc); 2291 if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) { 2292 ToolingExtensions.removeExtension(dest, ex.getUrl()); 2293 } 2294 dest.addExtension(ex.copy()); 2295 } 2296 } 2297 if (dest.hasFixed()) { 2298 checkTypeOk(dest, dest.getFixed().fhirType(), srcSD, "fixed"); 2299 } 2300 if (dest.hasPattern()) { 2301 checkTypeOk(dest, dest.getPattern().fhirType(), srcSD, "pattern"); 2302 } 2303 } 2304 2305 private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts) { 2306 boolean ok = false; 2307 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2308 String t = ts.getWorkingCode(); 2309 for (TypeRefComponent td : base.getType()) {; 2310 String tt = td.getWorkingCode(); 2311 b.append(tt); 2312 if (td.hasCode() && (tt.equals(t))) { 2313 ok = true; 2314 } 2315 if (!ok) { 2316 StructureDefinition sdt = context.fetchTypeDefinition(tt); 2317 if (sdt != null && (sdt.getAbstract() || sdt.getKind() == StructureDefinitionKind.LOGICAL)) { 2318 StructureDefinition sdb = context.fetchTypeDefinition(t); 2319 while (sdb != null && !ok) { 2320 ok = sdb.getType().equals(sdt.getType()); 2321 sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition(), sdb); 2322 } 2323 } 2324 } 2325 // work around for old badly generated SDs 2326 if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) { 2327 ok = true; 2328 } 2329 if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) { 2330 ok = true; 2331 } 2332 if (ok && ts.hasTargetProfile()) { 2333 // check that any derived target has a reference chain back to one of the base target profiles 2334 for (UriType u : ts.getTargetProfile()) { 2335 String url = u.getValue(); 2336 boolean tgtOk = !td.hasTargetProfile() || td.hasTargetProfile(url); 2337 while (url != null && !tgtOk) { 2338 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 2339 if (sd == null) { 2340 if (messages != null) { 2341 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, purl+"#"+derived.getPath(), "Cannot check whether the target profile "+url+" is valid constraint on the base because it is not known", IssueSeverity.WARNING)); 2342 } 2343 url = null; 2344 tgtOk = true; // suppress error message 2345 } else { 2346 url = sd.getBaseDefinition(); 2347 tgtOk = td.hasTargetProfile(url); 2348 } 2349 } 2350 if (!tgtOk) { 2351 if (messages == null) { 2352 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT__THE_TARGET_PROFILE__IS_NOT__VALID_CONSTRAINT_ON_THE_BASE_, purl, derived.getPath(), url, td.getTargetProfile())); 2353 } else { 2354 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, derived.getPath(), "The target profile "+u.getValue()+" is not a valid constraint on the base ("+td.getTargetProfile()+") at "+derived.getPath(), IssueSeverity.ERROR)); 2355 } 2356 } 2357 } 2358 } 2359 } 2360 if (!ok) { 2361 throw new DefinitionException(context.formatMessage(I18nConstants.STRUCTUREDEFINITION__AT__ILLEGAL_CONSTRAINED_TYPE__FROM__IN_, purl, derived.getPath(), t, b.toString(), srcSD.getUrl())); 2362 } 2363 } 2364 2365 2366 private void checkTypeOk(ElementDefinition dest, String ft, StructureDefinition sd, String fieldName) { 2367 boolean ok = false; 2368 Set<String> types = new HashSet<>(); 2369 if (dest.getPath().contains(".")) { 2370 for (TypeRefComponent t : dest.getType()) { 2371 if (t.hasCode()) { 2372 types.add(t.getWorkingCode()); 2373 } 2374 ok = ft.equals(t.getWorkingCode()); 2375 } 2376 } else { 2377 types.add(sd.getType()); 2378 ok = ft.equals(sd.getType()); 2379 2380 } 2381 if (!ok) { 2382 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.CONFLICT, dest.getId(), "The "+fieldName+" value has type '"+ft+"' which is not valid (valid "+Utilities.pluralize("type", dest.getType().size())+": "+types.toString()+")", IssueSeverity.ERROR)); 2383 } 2384 } 2385 2386 private boolean hasBindableType(ElementDefinition ed) { 2387 for (TypeRefComponent tr : ed.getType()) { 2388 if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code", "CodeableReference")) { 2389 return true; 2390 } 2391 StructureDefinition sd = context.fetchTypeDefinition(tr.getCode()); 2392 if (sd != null && sd.hasExtension(ToolingExtensions.EXT_BINDING_STYLE)) { 2393 return true; 2394 } 2395 } 2396 return false; 2397 } 2398 2399 2400 private boolean isLargerMax(String derived, String base) { 2401 if ("*".equals(base)) { 2402 return false; 2403 } 2404 if ("*".equals(derived)) { 2405 return true; 2406 } 2407 return Integer.parseInt(derived) > Integer.parseInt(base); 2408 } 2409 2410 2411 private boolean isSubset(ValueSet expBase, ValueSet expDerived) { 2412 return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion()); 2413 } 2414 2415 2416 private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) { 2417 for (ValueSetExpansionContainsComponent cc : contains) { 2418 if (!inExpansion(cc, expansion.getContains())) { 2419 return false; 2420 } 2421 if (!codesInExpansion(cc.getContains(), expansion)) { 2422 return false; 2423 } 2424 } 2425 return true; 2426 } 2427 2428 2429 private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) { 2430 for (ValueSetExpansionContainsComponent cc1 : contains) { 2431 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) { 2432 return true; 2433 } 2434 if (inExpansion(cc, cc1.getContains())) { 2435 return true; 2436 } 2437 } 2438 return false; 2439 } 2440 2441 public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { 2442 for (ElementDefinition edb : base.getSnapshot().getElement()) { 2443 if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { 2444 ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); 2445 if (edm == null) { 2446 ElementDefinition edd = derived.getDifferential().addElement(); 2447 edd.setPath(edb.getPath()); 2448 edd.setMax("0"); 2449 } else if (edb.hasSlicing()) { 2450 closeChildren(base, edb, derived, edm); 2451 } 2452 } 2453 } 2454 sortDifferential(base, derived, derived.getName(), new ArrayList<String>(), false); 2455 } 2456 2457 private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) { 2458// String path = edb.getPath()+"."; 2459 int baseStart = base.getSnapshot().getElement().indexOf(edb); 2460 int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1); 2461 int diffStart = derived.getDifferential().getElement().indexOf(edm); 2462 int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1); 2463 2464 for (int cBase = baseStart; cBase < baseEnd; cBase++) { 2465 ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); 2466 if (isImmediateChild(edBase, edb)) { 2467 ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd); 2468 if (edMatch == null) { 2469 ElementDefinition edd = derived.getDifferential().addElement(); 2470 edd.setPath(edBase.getPath()); 2471 edd.setMax("0"); 2472 } else { 2473 closeChildren(base, edBase, derived, edMatch); 2474 } 2475 } 2476 } 2477 } 2478 2479 private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) { 2480 String path = ed.getPath()+"."; 2481 while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) { 2482 cursor++; 2483 } 2484 return cursor; 2485 } 2486 2487 2488 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) { 2489 for (ElementDefinition t : list) { 2490 if (t.getPath().equals(ed.getPath())) { 2491 return t; 2492 } 2493 } 2494 return null; 2495 } 2496 2497 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) { 2498 for (int i = start; i < end; i++) { 2499 ElementDefinition t = list.get(i); 2500 if (t.getPath().equals(ed.getPath())) { 2501 return t; 2502 } 2503 } 2504 return null; 2505 } 2506 2507 2508 private boolean isImmediateChild(ElementDefinition ed) { 2509 String p = ed.getPath(); 2510 if (!p.contains(".")) { 2511 return false; 2512 } 2513 p = p.substring(p.indexOf(".")+1); 2514 return !p.contains("."); 2515 } 2516 2517 private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { 2518 String p = candidate.getPath(); 2519 if (!p.contains(".")) 2520 return false; 2521 if (!p.startsWith(base.getPath()+".")) 2522 return false; 2523 p = p.substring(base.getPath().length()+1); 2524 return !p.contains("."); 2525 } 2526 2527 2528 2529 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 2530 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 2531 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 2532 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url")) 2533 return ed.getSnapshot().getElement().get(i); 2534 i++; 2535 } 2536 return null; 2537 } 2538 2539 2540 2541 protected ElementDefinitionResolution getElementById(StructureDefinition source, List<ElementDefinition> elements, String contentReference) { 2542 if (!contentReference.startsWith("#") && contentReference.contains("#")) { 2543 String url = contentReference.substring(0, contentReference.indexOf("#")); 2544 contentReference = contentReference.substring(contentReference.indexOf("#")); 2545 if (!url.equals(source.getUrl())){ 2546 source = context.fetchResource(StructureDefinition.class, url, source); 2547 if (source == null) { 2548 return null; 2549 } 2550 elements = source.getSnapshot().getElement(); 2551 } 2552 } 2553 for (ElementDefinition ed : elements) 2554 if (ed.hasId() && ("#"+ed.getId()).equals(contentReference)) 2555 return new ElementDefinitionResolution(source, ed); 2556 return null; 2557 } 2558 2559 2560 public static String describeExtensionContext(StructureDefinition ext) { 2561 StringBuilder b = new StringBuilder(); 2562 b.append("Use on "); 2563 for (int i = 0; i < ext.getContext().size(); i++) { 2564 StructureDefinitionContextComponent ec = ext.getContext().get(i); 2565 if (i > 0) 2566 b.append(i < ext.getContext().size() - 1 ? ", " : " or "); 2567 b.append(ec.getType().getDisplay()); 2568 b.append(" "); 2569 b.append(ec.getExpression()); 2570 } 2571 if (ext.hasContextInvariant()) { 2572 b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = "); 2573 boolean first = true; 2574 for (StringType s : ext.getContextInvariant()) { 2575 if (first) 2576 first = false; 2577 else 2578 b.append(", "); 2579 b.append("<code>"+s.getValue()+"</code>"); 2580 } 2581 } 2582 return b.toString(); 2583 } 2584 2585 2586 2587 2588// public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, 2589// boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean mustSupport, RenderingContext rc) throws IOException, FHIRException { 2590// return generateTable(defFile, profile, diff, imageFolder, inlineGraphics, profileBaseFileName, snapshot, corePath, imagePath, logicalModel, allInvariants, outputTracker, mustSupport, rc, ""); 2591// } 2592 2593 2594 2595 2596 2597 protected String tail(String path) { 2598 if (path == null) { 2599 return ""; 2600 } else if (path.contains(".")) 2601 return path.substring(path.lastIndexOf('.')+1); 2602 else 2603 return path; 2604 } 2605 2606 private boolean isDataType(String value) { 2607 StructureDefinition sd = context.fetchTypeDefinition(value); 2608 if (sd == null) // might be running before all SDs are available 2609 return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", 2610 "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext"); 2611 else 2612 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 2613 } 2614 2615 private boolean isConstrainedDataType(String value) { 2616 StructureDefinition sd = context.fetchTypeDefinition(value); 2617 if (sd == null) // might be running before all SDs are available 2618 return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"); 2619 else 2620 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT; 2621 } 2622 2623 private String baseType(String value) { 2624 StructureDefinition sd = context.fetchTypeDefinition(value); 2625 if (sd != null) // might be running before all SDs are available 2626 return sd.getTypeName(); 2627 if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity")) 2628 return "Quantity"; 2629 throw new Error(context.formatMessage(I18nConstants.INTERNAL_ERROR___TYPE_NOT_KNOWN_, value)); 2630 } 2631 2632 2633 protected boolean isPrimitive(String value) { 2634 StructureDefinition sd = context.fetchTypeDefinition(value); 2635 if (sd == null) // might be running before all SDs are available 2636 return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid"); 2637 else 2638 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 2639 } 2640 2641// private static String listStructures(StructureDefinition p) { 2642// StringBuilder b = new StringBuilder(); 2643// boolean first = true; 2644// for (ProfileStructureComponent s : p.getStructure()) { 2645// if (first) 2646// first = false; 2647// else 2648// b.append(", "); 2649// if (pkp != null && pkp.hasLinkFor(s.getType())) 2650// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 2651// else 2652// b.append(s.getType()); 2653// } 2654// return b.toString(); 2655// } 2656 2657 2658 public StructureDefinition getProfile(StructureDefinition source, String url) { 2659 StructureDefinition profile = null; 2660 String code = null; 2661 if (url.startsWith("#")) { 2662 profile = source; 2663 code = url.substring(1); 2664 } else if (context != null) { 2665 String[] parts = url.split("\\#"); 2666 profile = context.fetchResource(StructureDefinition.class, parts[0], source); 2667 code = parts.length == 1 ? null : parts[1]; 2668 } 2669 if (profile == null) 2670 return null; 2671 if (code == null) 2672 return profile; 2673 for (Resource r : profile.getContained()) { 2674 if (r instanceof StructureDefinition && r.getId().equals(code)) 2675 return (StructureDefinition) r; 2676 } 2677 return null; 2678 } 2679 2680 2681 2682 private static class ElementDefinitionHolder { 2683 private String name; 2684 private ElementDefinition self; 2685 private int baseIndex = 0; 2686 private List<ElementDefinitionHolder> children; 2687 private boolean placeHolder = false; 2688 2689 public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) { 2690 super(); 2691 this.self = self; 2692 this.name = self.getPath(); 2693 this.placeHolder = isPlaceholder; 2694 children = new ArrayList<ElementDefinitionHolder>(); 2695 } 2696 2697 public ElementDefinitionHolder(ElementDefinition self) { 2698 this(self, false); 2699 } 2700 2701 public ElementDefinition getSelf() { 2702 return self; 2703 } 2704 2705 public List<ElementDefinitionHolder> getChildren() { 2706 return children; 2707 } 2708 2709 public int getBaseIndex() { 2710 return baseIndex; 2711 } 2712 2713 public void setBaseIndex(int baseIndex) { 2714 this.baseIndex = baseIndex; 2715 } 2716 2717 public boolean isPlaceHolder() { 2718 return this.placeHolder; 2719 } 2720 2721 @Override 2722 public String toString() { 2723 if (self.hasSliceName()) 2724 return self.getPath()+"("+self.getSliceName()+")"; 2725 else 2726 return self.getPath(); 2727 } 2728 } 2729 2730 private static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 2731 2732 private boolean inExtension; 2733 private List<ElementDefinition> snapshot; 2734 private int prefixLength; 2735 private String base; 2736 private String name; 2737 private String baseName; 2738 private Set<String> errors = new HashSet<String>(); 2739 2740 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name, String baseName) { 2741 this.inExtension = inExtension; 2742 this.snapshot = snapshot; 2743 this.prefixLength = prefixLength; 2744 this.base = base; 2745 if (Utilities.isAbsoluteUrl(base)) { 2746 this.base = urlTail(base); 2747 } 2748 this.name = name; 2749 this.baseName = baseName; 2750 } 2751 2752 @Override 2753 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 2754 if (o1.getBaseIndex() == 0) 2755 o1.setBaseIndex(find(o1.getSelf().getPath(), true)); 2756 if (o2.getBaseIndex() == 0) 2757 o2.setBaseIndex(find(o2.getSelf().getPath(), true)); 2758 return o1.getBaseIndex() - o2.getBaseIndex(); 2759 } 2760 2761 private int find(String path, boolean mandatory) { 2762 String op = path; 2763 int lc = 0; 2764 String actual = base+path.substring(prefixLength); 2765 for (int i = 0; i < snapshot.size(); i++) { 2766 String p = snapshot.get(i).getPath(); 2767 if (p.equals(actual)) { 2768 return i; 2769 } 2770 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) { 2771 return i; 2772 } 2773 if (actual.endsWith("[x]") && p.startsWith(actual.substring(0, actual.length()-3)) && !p.substring(actual.length()-3).contains(".")) { 2774 return i; 2775 } 2776 if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) { 2777 String ref = snapshot.get(i).getContentReference(); 2778 if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) { 2779 actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 2780 path = actual; 2781 } else if (ref.startsWith("http:")) { 2782 actual = base+(ref.substring(ref.indexOf("#")+1)+"."+path.substring(p.length()+1)).substring(prefixLength); 2783 path = actual; 2784 } else { 2785 // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that 2786 actual = base+(path.substring(0, path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 2787 path = actual; 2788 } 2789 2790 i = 0; 2791 lc++; 2792 if (lc > MAX_RECURSION_LIMIT) 2793 throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")"); 2794 } 2795 } 2796 if (mandatory) { 2797 if (prefixLength == 0) 2798 errors.add("Differential contains path "+path+" which is not found in the in base "+baseName); 2799 else 2800 errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+ baseName); 2801 } 2802 return 0; 2803 } 2804 2805 public void checkForErrors(List<String> errorList) { 2806 if (errors.size() > 0) { 2807// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2808// for (String s : errors) 2809// b.append("StructureDefinition "+name+": "+s); 2810// throw new DefinitionException(b.toString()); 2811 for (String s : errors) 2812 if (s.startsWith("!")) 2813 errorList.add("!StructureDefinition "+name+": "+s.substring(1)); 2814 else 2815 errorList.add("StructureDefinition "+name+": "+s); 2816 } 2817 } 2818 } 2819 2820 2821 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors, boolean errorIfChanges) throws FHIRException { 2822 List<ElementDefinition> original = new ArrayList<>(); 2823 original.addAll(diff.getDifferential().getElement()); 2824 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 2825 int lastCount = diffList.size(); 2826 // first, we move the differential elements into a tree 2827 if (diffList.isEmpty()) 2828 return; 2829 2830 ElementDefinitionHolder edh = null; 2831 int i = 0; 2832 if (diffList.get(0).getPath().contains(".")) { 2833 String newPath = diffList.get(0).getPath().split("\\.")[0]; 2834 ElementDefinition e = new ElementDefinition(newPath); 2835 edh = new ElementDefinitionHolder(e, true); 2836 } else { 2837 edh = new ElementDefinitionHolder(diffList.get(0)); 2838 i = 1; 2839 } 2840 2841 boolean hasSlicing = false; 2842 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 2843 for(ElementDefinition elt : diffList) { 2844 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 2845 hasSlicing = true; 2846 break; 2847 } 2848 paths.add(elt.getPath()); 2849 } 2850 if(!hasSlicing) { 2851 // if Differential does not have slicing then safe to pre-sort the list 2852 // so elements and subcomponents are together 2853 Collections.sort(diffList, new ElementNameCompare()); 2854 } 2855 2856 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 2857 2858 // now, we sort the siblings throughout the tree 2859 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name, base.getType()); 2860 sortElements(edh, cmp, errors); 2861 2862 // now, we serialise them back to a list 2863 List<ElementDefinition> newDiff = new ArrayList<>(); 2864 writeElements(edh, newDiff); 2865 if (errorIfChanges) { 2866 compareDiffs(original, newDiff, errors); 2867 } 2868 diffList.clear(); 2869 diffList.addAll(newDiff); 2870 2871 if (lastCount != diffList.size()) 2872 errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal"); 2873 } 2874 2875 private void compareDiffs(List<ElementDefinition> diffList, List<ElementDefinition> newDiff, List<String> errors) { 2876 if (diffList.size() != newDiff.size()) { 2877 errors.add("The diff list size changed when sorting - was "+diffList.size()+" is now "+newDiff.size()); 2878 } else { 2879 for (int i = 0; i < Integer.min(diffList.size(), newDiff.size()); i++) { 2880 ElementDefinition e = diffList.get(i); 2881 ElementDefinition n = newDiff.get(i); 2882 if (!n.getPath().equals(e.getPath())) { 2883 errors.add("The element "+e.getPath()+" is out of order (and maybe others after it)"); 2884 return; 2885 } 2886 } 2887 } 2888 } 2889 2890 2891 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 2892 String path = edh.getSelf().getPath(); 2893 final String prefix = path + "."; 2894 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 2895 if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) { 2896 String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0]; 2897 ElementDefinition e = new ElementDefinition(newPath); 2898 ElementDefinitionHolder child = new ElementDefinitionHolder(e, true); 2899 edh.getChildren().add(child); 2900 i = processElementsIntoTree(child, i, list); 2901 2902 } else { 2903 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 2904 edh.getChildren().add(child); 2905 i = processElementsIntoTree(child, i+1, list); 2906 } 2907 } 2908 return i; 2909 } 2910 2911 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException { 2912 if (edh.getChildren().size() == 1) 2913 // special case - sort needsto allocate base numbers, but there'll be no sort if there's only 1 child. So in that case, we just go ahead and allocated base number directly 2914 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false); 2915 else 2916 Collections.sort(edh.getChildren(), cmp); 2917 cmp.checkForErrors(errors); 2918 2919 for (ElementDefinitionHolder child : edh.getChildren()) { 2920 if (child.getChildren().size() > 0) { 2921 ElementDefinitionComparer ccmp = getComparer(cmp, child); 2922 if (ccmp != null) { 2923 sortElements(child, ccmp, errors); 2924 } 2925 } 2926 } 2927 } 2928 2929 2930 public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error { 2931 // what we have to check for here is running off the base profile into a data type profile 2932 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 2933 ElementDefinitionComparer ccmp; 2934 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) { 2935 if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && (child.getSelf().hasType() && child.getSelf().getType().get(0).hasProfile())) { 2936 if (child.getSelf().getType().get(0).getProfile().size() > 1) { 2937 throw new FHIRException(context.formatMessage(I18nConstants.UNHANDLED_SITUATION_RESOURCE_IS_PROFILED_TO_MORE_THAN_ONE_OPTION__CANNOT_SORT_PROFILE)); 2938 } 2939 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 2940 while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 2941 profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition()); 2942 } 2943 if (profile==null) { 2944 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 2945 } else { 2946 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name, profile.present()); 2947 } 2948 } else { 2949 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name, cmp.name); 2950 } 2951 } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) { 2952 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 2953 if (profile==null) 2954 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 2955 else 2956 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present()); 2957 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) { 2958 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 2959 if (profile==null) 2960 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 2961 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present()); 2962 } else if (child.getSelf().getType().size() == 1) { 2963 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode())); 2964 if (profile==null) 2965 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 2966 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 2967 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 2968 String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2"); 2969 String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2"); 2970 String p = childLastNode.substring(edLastNode.length()-3); 2971 if (isPrimitive(Utilities.uncapitalize(p))) 2972 p = Utilities.uncapitalize(p); 2973 StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p)); 2974 if (sd == null) 2975 throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId())); 2976 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name, sd.present()); 2977 } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) { 2978 for (TypeRefComponent t: child.getSelf().getType()) { 2979 if (!t.getWorkingCode().equals("Reference")) { 2980 throw new Error(context.formatMessage(I18nConstants.CANT_HAVE_CHILDREN_ON_AN_ELEMENT_WITH_A_POLYMORPHIC_TYPE__YOU_MUST_SLICE_AND_CONSTRAIN_THE_TYPES_FIRST_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType()))); 2981 } 2982 } 2983 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 2984 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 2985 } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) { 2986 for (TypeRefComponent t: ed.getType()) { 2987 if (!t.getWorkingCode().equals("Reference")) { 2988 throw new Error(context.formatMessage(I18nConstants.NOT_HANDLED_YET_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType()))); 2989 } 2990 } 2991 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 2992 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 2993 } else { 2994 // this is allowed if we only profile the extensions 2995 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element")); 2996 if (profile==null) 2997 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 2998 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name, profile.present()); 2999// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3000 } 3001 return ccmp; 3002 } 3003 3004 private String resolveType(String code) { 3005 if (Utilities.isAbsoluteUrl(code)) { 3006 StructureDefinition sd = context.fetchResource(StructureDefinition.class, code); 3007 if (sd != null) { 3008 return sd.getType(); 3009 } 3010 } 3011 return code; 3012 } 3013 3014 private static String sdNs(String type) { 3015 return sdNs(type, null); 3016 } 3017 3018 public static String sdNs(String type, String overrideVersionNs) { 3019 if (Utilities.isAbsoluteUrl(type)) 3020 return type; 3021 else if (overrideVersionNs != null) 3022 return Utilities.pathURL(overrideVersionNs, type); 3023 else 3024 return "http://hl7.org/fhir/StructureDefinition/"+type; 3025 } 3026 3027 3028 private boolean isAbstract(String code) { 3029 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource"); 3030 } 3031 3032 3033 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 3034 if (!edh.isPlaceHolder()) 3035 list.add(edh.getSelf()); 3036 for (ElementDefinitionHolder child : edh.getChildren()) { 3037 writeElements(child, list); 3038 } 3039 } 3040 3041 /** 3042 * First compare element by path then by name if same 3043 */ 3044 private static class ElementNameCompare implements Comparator<ElementDefinition> { 3045 3046 @Override 3047 public int compare(ElementDefinition o1, ElementDefinition o2) { 3048 String path1 = normalizePath(o1); 3049 String path2 = normalizePath(o2); 3050 int cmp = path1.compareTo(path2); 3051 if (cmp == 0) { 3052 String name1 = o1.hasSliceName() ? o1.getSliceName() : ""; 3053 String name2 = o2.hasSliceName() ? o2.getSliceName() : ""; 3054 cmp = name1.compareTo(name2); 3055 } 3056 return cmp; 3057 } 3058 3059 private static String normalizePath(ElementDefinition e) { 3060 if (!e.hasPath()) return ""; 3061 String path = e.getPath(); 3062 // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc. 3063 // so strip off the [x] suffix when comparing the path names. 3064 if (path.endsWith("[x]")) { 3065 path = path.substring(0, path.length()-3); 3066 } 3067 return path; 3068 } 3069 3070 } 3071 3072 3073 // generate schematrons for the rules in a structure definition 3074 public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException { 3075 if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT) 3076 throw new DefinitionException(context.formatMessage(I18nConstants.NOT_THE_RIGHT_KIND_OF_STRUCTURE_TO_GENERATE_SCHEMATRONS_FOR)); 3077 if (!structure.hasSnapshot()) 3078 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 3079 3080 StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition(), structure); 3081 3082 if (base != null) { 3083 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 3084 3085 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 3086 generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base); 3087 sch.dump(); 3088 } 3089 } 3090 3091 // generate a CSV representation of the structure definition 3092 public void generateCsv(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception { 3093 if (!structure.hasSnapshot()) 3094 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 3095 3096 CSVWriter csv = new CSVWriter(dest, structure, asXml); 3097 3098 for (ElementDefinition child : structure.getSnapshot().getElement()) { 3099 csv.processElement(null, child); 3100 } 3101 csv.dump(); 3102 } 3103 3104 // generate a CSV representation of the structure definition 3105 public void addToCSV(CSVWriter csv, StructureDefinition structure) throws IOException, DefinitionException, Exception { 3106 if (!structure.hasSnapshot()) 3107 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 3108 3109 for (ElementDefinition child : structure.getSnapshot().getElement()) { 3110 csv.processElement(structure, child); 3111 } 3112 } 3113 3114 3115 private class Slicer extends ElementDefinitionSlicingComponent { 3116 String criteria = ""; 3117 String name = ""; 3118 boolean check; 3119 public Slicer(boolean cantCheck) { 3120 super(); 3121 this.check = cantCheck; 3122 } 3123 } 3124 3125 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) { 3126 // given a child in a structure, it's sliced. figure out the slicing xpath 3127 if (child.getPath().endsWith(".extension")) { 3128 ElementDefinition ued = getUrlFor(structure, child); 3129 if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile()))) 3130 return new Slicer(false); 3131 else { 3132 Slicer s = new Slicer(true); 3133 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue(); 3134 s.name = " with URL = '"+url+"'"; 3135 s.criteria = "[@url = '"+url+"']"; 3136 return s; 3137 } 3138 } else 3139 return new Slicer(false); 3140 } 3141 3142 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException { 3143 // generateForChild(txt, structure, child); 3144 List<ElementDefinition> children = getChildList(structure, ed); 3145 String sliceName = null; 3146 ElementDefinitionSlicingComponent slicing = null; 3147 for (ElementDefinition child : children) { 3148 String name = tail(child.getPath()); 3149 if (child.hasSlicing()) { 3150 sliceName = name; 3151 slicing = child.getSlicing(); 3152 } else if (!name.equals(sliceName)) 3153 slicing = null; 3154 3155 ElementDefinition based = getByPath(base, child.getPath()); 3156 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 3157 boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax()))); 3158 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 3159 if (slicer.check) { 3160 if (doMin || doMax) { 3161 Section s = sch.section(xpath); 3162 Rule r = s.rule(xpath); 3163 if (doMin) 3164 r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin())); 3165 if (doMax) 3166 r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax()); 3167 } 3168 } 3169 } 3170/// xpath has been removed 3171// for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 3172// if (inv.hasXpath()) { 3173// Section s = sch.section(ed.getPath()); 3174// Rule r = s.rule(xpath); 3175// r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 3176// } 3177// } 3178 if (!ed.hasContentReference()) { 3179 for (ElementDefinition child : children) { 3180 String name = tail(child.getPath()); 3181 generateForChildren(sch, xpath+"/f:"+name, child, structure, base); 3182 } 3183 } 3184 } 3185 3186 3187 3188 3189 private ElementDefinition getByPath(StructureDefinition base, String path) { 3190 for (ElementDefinition ed : base.getSnapshot().getElement()) { 3191 if (ed.getPath().equals(path)) 3192 return ed; 3193 if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length()-3 && ed.getPath().substring(0, ed.getPath().length()-3).equals(path.substring(0, ed.getPath().length()-3))) 3194 return ed; 3195 } 3196 return null; 3197 } 3198 3199 3200 public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException { 3201 if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { 3202 if (!sd.hasDifferential()) 3203 sd.setDifferential(new StructureDefinitionDifferentialComponent()); 3204 generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType(), sd); 3205 } 3206 if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { 3207 if (!sd.hasSnapshot()) 3208 sd.setSnapshot(new StructureDefinitionSnapshotComponent()); 3209 generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType(), sd); 3210 } 3211 } 3212 3213 3214 private boolean hasMissingIds(List<ElementDefinition> list) { 3215 for (ElementDefinition ed : list) { 3216 if (!ed.hasId()) 3217 return true; 3218 } 3219 return false; 3220 } 3221 3222 private class SliceList { 3223 3224 private Map<String, String> slices = new HashMap<>(); 3225 3226 public void seeElement(ElementDefinition ed) { 3227 Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator(); 3228 while (iter.hasNext()) { 3229 Map.Entry<String,String> entry = iter.next(); 3230 if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath())) 3231 iter.remove(); 3232 } 3233 3234 if (ed.hasSliceName()) 3235 slices.put(ed.getPath(), ed.getSliceName()); 3236 } 3237 3238 public String[] analyse(List<String> paths) { 3239 String s = paths.get(0); 3240 String[] res = new String[paths.size()]; 3241 res[0] = null; 3242 for (int i = 1; i < paths.size(); i++) { 3243 s = s + "."+paths.get(i); 3244 if (slices.containsKey(s)) 3245 res[i] = slices.get(s); 3246 else 3247 res[i] = null; 3248 } 3249 return res; 3250 } 3251 3252 } 3253 3254 protected void generateIds(List<ElementDefinition> list, String name, String type, StructureDefinition srcSD) throws DefinitionException { 3255 if (list.isEmpty()) 3256 return; 3257 3258 Map<String, String> idList = new HashMap<String, String>(); 3259 Map<String, String> replacedIds = new HashMap<String, String>(); 3260 3261 SliceList sliceInfo = new SliceList(); 3262 // first pass, update the element ids 3263 for (ElementDefinition ed : list) { 3264 List<String> paths = new ArrayList<String>(); 3265 if (!ed.hasPath()) 3266 throw new DefinitionException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_DEFINITION__IN_, Integer.toString(list.indexOf(ed)), name)); 3267 sliceInfo.seeElement(ed); 3268 String[] pl = ed.getPath().split("\\."); 3269 for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus 3270 paths.add(pl[i]); 3271 String slices[] = sliceInfo.analyse(paths); 3272 3273 StringBuilder b = new StringBuilder(); 3274 b.append(paths.get(0)); 3275 for (int i = 1; i < paths.size(); i++) { 3276 b.append("."); 3277 String s = paths.get(i); 3278 String p = slices[i]; 3279 b.append(fixChars(s)); 3280 if (p != null) { 3281 b.append(":"); 3282 b.append(p); 3283 } 3284 } 3285 String bs = b.toString(); 3286 if (ed.hasId()) { 3287 replacedIds.put(ed.getId(), ed.getPath()); 3288 } 3289 ed.setId(bs); 3290 if (idList.containsKey(bs)) { 3291 if (exception || messages == null) { 3292 throw new DefinitionException(context.formatMessage(I18nConstants.SAME_ID_ON_MULTIPLE_ELEMENTS__IN_, bs, idList.get(bs), ed.getPath(), name)); 3293 } else 3294 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR)); 3295 } 3296 idList.put(bs, ed.getPath()); 3297 if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) { 3298 String s = ed.getContentReference(); 3299 String typeURL = getUrlForSource(type, srcSD); 3300 if (replacedIds.containsKey(s.substring(1))) { 3301 ed.setContentReference(typeURL+"#"+replacedIds.get(s.substring(1))); 3302 } else { 3303 ed.setContentReference(typeURL+s); 3304 } 3305 } 3306 } 3307 // second path - fix up any broken path based id references 3308 3309 } 3310 3311 3312 private String getUrlForSource(String type, StructureDefinition srcSD) { 3313 if (srcSD.getKind() == StructureDefinitionKind.LOGICAL) { 3314 return srcSD.getUrl(); 3315 } else { 3316 return "http://hl7.org/fhir/StructureDefinition/"+type; 3317 } 3318 } 3319 3320 private Object fixChars(String s) { 3321 return s.replace("_", "-"); 3322 } 3323 3324 3325// private String describeExtension(ElementDefinition ed) { 3326// if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) 3327// return ""; 3328// return "$"+urlTail(ed.getTypeFirstRep().getProfile()); 3329// } 3330// 3331 3332 private static String urlTail(String profile) { 3333 return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile; 3334 } 3335// 3336// 3337// private String checkName(String name) { 3338//// if (name.contains(".")) 3339////// throw new Exception("Illegal name "+name+": no '.'"); 3340//// if (name.contains(" ")) 3341//// throw new Exception("Illegal name "+name+": no spaces"); 3342// StringBuilder b = new StringBuilder(); 3343// for (char c : name.toCharArray()) { 3344// if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']')) 3345// b.append(c); 3346// } 3347// return b.toString().toLowerCase(); 3348// } 3349// 3350// 3351// private int charCount(String path, char t) { 3352// int res = 0; 3353// for (char ch : path.toCharArray()) { 3354// if (ch == t) 3355// res++; 3356// } 3357// return res; 3358// } 3359 3360// 3361//private void generateForChild(TextStreamWriter txt, 3362// StructureDefinition structure, ElementDefinition child) { 3363// // TODO Auto-generated method stub 3364// 3365//} 3366 3367 private interface ExampleValueAccessor { 3368 DataType getExampleValue(ElementDefinition ed); 3369 String getId(); 3370 } 3371 3372 private class BaseExampleValueAccessor implements ExampleValueAccessor { 3373 @Override 3374 public DataType getExampleValue(ElementDefinition ed) { 3375 if (ed.hasFixed()) 3376 return ed.getFixed(); 3377 if (ed.hasExample()) 3378 return ed.getExample().get(0).getValue(); 3379 else 3380 return null; 3381 } 3382 3383 @Override 3384 public String getId() { 3385 return "-genexample"; 3386 } 3387 } 3388 3389 private class ExtendedExampleValueAccessor implements ExampleValueAccessor { 3390 private String index; 3391 3392 public ExtendedExampleValueAccessor(String index) { 3393 this.index = index; 3394 } 3395 @Override 3396 public DataType getExampleValue(ElementDefinition ed) { 3397 if (ed.hasFixed()) 3398 return ed.getFixed(); 3399 for (Extension ex : ed.getExtension()) { 3400 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 3401 DataType value = ToolingExtensions.getExtension(ex, "exValue").getValue(); 3402 if (index.equals(ndx) && value != null) 3403 return value; 3404 } 3405 return null; 3406 } 3407 @Override 3408 public String getId() { 3409 return "-genexample-"+index; 3410 } 3411 } 3412 3413 public List<org.hl7.fhir.r5.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException { 3414 List<org.hl7.fhir.r5.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r5.elementmodel.Element>(); 3415 if (sd.hasSnapshot()) { 3416 if (evenWhenNoExamples || hasAnyExampleValues(sd)) 3417 examples.add(generateExample(sd, new BaseExampleValueAccessor())); 3418 for (int i = 1; i <= 50; i++) { 3419 if (hasAnyExampleValues(sd, Integer.toString(i))) 3420 examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i)))); 3421 } 3422 } 3423 return examples; 3424 } 3425 3426 private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException { 3427 ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 3428 org.hl7.fhir.r5.elementmodel.Element r = new org.hl7.fhir.r5.elementmodel.Element(ed.getPath(), new Property(context, ed, profile)); 3429 SourcedChildDefinitions children = getChildMap(profile, ed); 3430 for (ElementDefinition child : children.getList()) { 3431 if (child.getPath().endsWith(".id")) { 3432 org.hl7.fhir.r5.elementmodel.Element id = new org.hl7.fhir.r5.elementmodel.Element("id", new Property(context, child, profile)); 3433 id.setValue(profile.getId()+accessor.getId()); 3434 r.getChildren().add(id); 3435 } else { 3436 org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); 3437 if (e != null) 3438 r.getChildren().add(e); 3439 } 3440 } 3441 return r; 3442 } 3443 3444 private org.hl7.fhir.r5.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException { 3445 DataType v = accessor.getExampleValue(ed); 3446 if (v != null) { 3447 return new ObjectConverter(context).convert(new Property(context, ed, profile), v); 3448 } else { 3449 org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile)); 3450 boolean hasValue = false; 3451 SourcedChildDefinitions children = getChildMap(profile, ed); 3452 for (ElementDefinition child : children.getList()) { 3453 if (!child.hasContentReference()) { 3454 org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); 3455 if (e != null) { 3456 hasValue = true; 3457 res.getChildren().add(e); 3458 } 3459 } 3460 } 3461 if (hasValue) 3462 return res; 3463 else 3464 return null; 3465 } 3466 } 3467 3468 private boolean hasAnyExampleValues(StructureDefinition sd, String index) { 3469 for (ElementDefinition ed : sd.getSnapshot().getElement()) 3470 for (Extension ex : ed.getExtension()) { 3471 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 3472 Extension exv = ToolingExtensions.getExtension(ex, "exValue"); 3473 if (exv != null) { 3474 DataType value = exv.getValue(); 3475 if (index.equals(ndx) && value != null) 3476 return true; 3477 } 3478 } 3479 return false; 3480 } 3481 3482 3483 private boolean hasAnyExampleValues(StructureDefinition sd) { 3484 for (ElementDefinition ed : sd.getSnapshot().getElement()) 3485 if (ed.hasExample()) 3486 return true; 3487 return false; 3488 } 3489 3490 3491 public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException { 3492 sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy()); 3493 3494 if (sd.hasBaseDefinition()) { 3495 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 3496 if (base == null) 3497 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE_DEFINITION_FOR_LOGICAL_MODEL__FROM_, sd.getBaseDefinition(), sd.getUrl())); 3498 copyElements(sd, base.getSnapshot().getElement()); 3499 } 3500 copyElements(sd, sd.getDifferential().getElement()); 3501 } 3502 3503 3504 private void copyElements(StructureDefinition sd, List<ElementDefinition> list) { 3505 for (ElementDefinition ed : list) { 3506 if (ed.getPath().contains(".")) { 3507 ElementDefinition n = ed.copy(); 3508 n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1)); 3509 sd.getSnapshot().addElement(n); 3510 } 3511 } 3512 } 3513 3514 3515 public void cleanUpDifferential(StructureDefinition sd) { 3516 if (sd.getDifferential().getElement().size() > 1) 3517 cleanUpDifferential(sd, 1); 3518 } 3519 3520 private void cleanUpDifferential(StructureDefinition sd, int start) { 3521 int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.'); 3522 int c = start; 3523 int len = sd.getDifferential().getElement().size(); 3524 HashSet<String> paths = new HashSet<String>(); 3525 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) { 3526 ElementDefinition ed = sd.getDifferential().getElement().get(c); 3527 if (!paths.contains(ed.getPath())) { 3528 paths.add(ed.getPath()); 3529 int ic = c+1; 3530 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 3531 ic++; 3532 ElementDefinition slicer = null; 3533 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 3534 slices.add(ed); 3535 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) { 3536 ElementDefinition edi = sd.getDifferential().getElement().get(ic); 3537 if (ed.getPath().equals(edi.getPath())) { 3538 if (slicer == null) { 3539 slicer = new ElementDefinition(); 3540 slicer.setPath(edi.getPath()); 3541 slicer.getSlicing().setRules(SlicingRules.OPEN); 3542 sd.getDifferential().getElement().add(c, slicer); 3543 c++; 3544 ic++; 3545 } 3546 slices.add(edi); 3547 } 3548 ic++; 3549 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 3550 ic++; 3551 } 3552 // now we're at the end, we're going to figure out the slicing discriminator 3553 if (slicer != null) 3554 determineSlicing(slicer, slices); 3555 } 3556 c++; 3557 if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) { 3558 cleanUpDifferential(sd, c); 3559 c++; 3560 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 3561 c++; 3562 } 3563 } 3564 } 3565 3566 3567 private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) { 3568 // first, name them 3569 int i = 0; 3570 for (ElementDefinition ed : slices) { 3571 if (ed.hasUserData("slice-name")) { 3572 ed.setSliceName(ed.getUserString("slice-name")); 3573 } else { 3574 i++; 3575 ed.setSliceName("slice-"+Integer.toString(i)); 3576 } 3577 } 3578 // now, the hard bit, how are they differentiated? 3579 // right now, we hard code this... 3580 if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension")) 3581 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 3582 else if (slicer.getPath().equals("DiagnosticReport.result")) 3583 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code"); 3584 else if (slicer.getPath().equals("Observation.related")) 3585 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code"); 3586 else if (slicer.getPath().equals("Bundle.entry")) 3587 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile"); 3588 else 3589 throw new Error("No slicing for "+slicer.getPath()); 3590 } 3591 3592 3593 public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) { 3594 if (discriminator.endsWith("@pattern")) 3595 return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 3596 if (discriminator.endsWith("@profile")) 3597 return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 3598 if (discriminator.endsWith("@type")) 3599 return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6)); 3600 if (discriminator.endsWith("@exists")) 3601 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 3602 if (isExists) 3603 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 3604 return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator); 3605 } 3606 3607 3608 private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) { 3609 return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str); 3610 } 3611 3612 3613 public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException { 3614 switch (t.getType()) { 3615 case PROFILE: return t.getPath()+"/@profile"; 3616 case PATTERN: return t.getPath()+"/@pattern"; 3617 case TYPE: return t.getPath()+"/@type"; 3618 case VALUE: return t.getPath(); 3619 case EXISTS: return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - one with minOccurs=1 and other with maxOccur=0 3620 default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2"); 3621 } 3622 } 3623 3624 3625 public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) { 3626 String epath = url.substring(54); 3627 if (!epath.contains(".")) 3628 return null; 3629 String type = epath.substring(0, epath.indexOf(".")); 3630 StructureDefinition sd = context.fetchTypeDefinition(type); 3631 if (sd == null) 3632 return null; 3633 ElementDefinition ed = null; 3634 for (ElementDefinition t : sd.getSnapshot().getElement()) { 3635 if (t.getPath().equals(epath)) { 3636 ed = t; 3637 break; 3638 } 3639 } 3640 if (ed == null) 3641 return null; 3642 if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) { 3643 return null; 3644 } else { 3645 StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"); 3646 StructureDefinition ext = template.copy(); 3647 ext.setUrl(url); 3648 ext.setId("extension-"+epath); 3649 ext.setName("Extension-"+epath); 3650 ext.setTitle("Extension for r4 "+epath); 3651 ext.setStatus(sd.getStatus()); 3652 ext.setDate(sd.getDate()); 3653 ext.getContact().clear(); 3654 ext.getContact().addAll(sd.getContact()); 3655 ext.setFhirVersion(sd.getFhirVersion()); 3656 ext.setDescription(ed.getDefinition()); 3657 ext.getContext().clear(); 3658 ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf("."))); 3659 ext.getDifferential().getElement().clear(); 3660 ext.getSnapshot().getElement().get(3).setFixed(new UriType(url)); 3661 ext.getSnapshot().getElement().set(4, ed.copy()); 3662 ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary())); 3663 return ext; 3664 } 3665 3666 } 3667 3668 3669 public boolean isThrowException() { 3670 return exception; 3671 } 3672 3673 3674 public void setThrowException(boolean exception) { 3675 this.exception = exception; 3676 } 3677 3678 3679 public ValidationOptions getTerminologyServiceOptions() { 3680 return terminologyServiceOptions; 3681 } 3682 3683 3684 public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) { 3685 this.terminologyServiceOptions = terminologyServiceOptions; 3686 } 3687 3688 3689 public boolean isNewSlicingProcessing() { 3690 return newSlicingProcessing; 3691 } 3692 3693 3694 public ProfileUtilities setNewSlicingProcessing(boolean newSlicingProcessing) { 3695 this.newSlicingProcessing = newSlicingProcessing; 3696 return this; 3697 } 3698 3699 3700 public boolean isDebug() { 3701 return debug; 3702 } 3703 3704 3705 public void setDebug(boolean debug) { 3706 this.debug = debug; 3707 } 3708 3709 3710 public String getDefWebRoot() { 3711 return defWebRoot; 3712 } 3713 3714 3715 public void setDefWebRoot(String defWebRoot) { 3716 this.defWebRoot = defWebRoot; 3717 if (!this.defWebRoot.endsWith("/")) 3718 this.defWebRoot = this.defWebRoot + '/'; 3719 } 3720 3721 3722 public static StructureDefinition makeBaseDefinition(FHIRVersion fhirVersion) { 3723 return makeBaseDefinition(fhirVersion.toCode()); 3724 } 3725 public static StructureDefinition makeBaseDefinition(String fhirVersion) { 3726 StructureDefinition base = new StructureDefinition(); 3727 base.setId("Base"); 3728 base.setUrl("http://hl7.org/fhir/StructureDefinition/Base"); 3729 base.setVersion(fhirVersion); 3730 base.setName("Base"); 3731 base.setStatus(PublicationStatus.ACTIVE); 3732 base.setDate(new Date()); 3733 base.setFhirVersion(FHIRVersion.fromCode(fhirVersion)); 3734 base.setKind(StructureDefinitionKind.COMPLEXTYPE); 3735 base.setAbstract(true); 3736 base.setType("Base"); 3737 base.setUserData("path", "http://build.fhir.org/types.html#Base"); 3738 ElementDefinition e = base.getSnapshot().getElementFirstRep(); 3739 e.setId("Base"); 3740 e.setPath("Base"); 3741 e.setMin(0); 3742 e.setMax("*"); 3743 e.getBase().setPath("Base"); 3744 e.getBase().setMin(0); 3745 e.getBase().setMax("*"); 3746 e.setIsModifier(false); 3747 e = base.getDifferential().getElementFirstRep(); 3748 e.setId("Base"); 3749 e.setPath("Base"); 3750 e.setMin(0); 3751 e.setMax("*"); 3752 return base; 3753 } 3754 3755 public XVerExtensionManager getXver() { 3756 return xver; 3757 } 3758 3759 public ProfileUtilities setXver(XVerExtensionManager xver) { 3760 this.xver = xver; 3761 return this; 3762 } 3763 3764 3765 private List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) { 3766 List<ElementChoiceGroup> result = new ArrayList<>(); 3767 for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { 3768 ElementChoiceGroup grp = processConstraint(children, c); 3769 if (grp != null) { 3770 result.add(grp); 3771 } 3772 } 3773 return result; 3774 } 3775 3776 public ElementChoiceGroup processConstraint(List<ElementDefinition> children, ElementDefinitionConstraintComponent c) { 3777 if (!c.hasExpression()) { 3778 return null; 3779 } 3780 ExpressionNode expr = null; 3781 try { 3782 expr = fpe.parse(c.getExpression()); 3783 } catch (Exception e) { 3784 return null; 3785 } 3786 if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) { 3787 return null; 3788 } 3789 ExpressionNode n1 = expr.getGroup(); 3790 ExpressionNode n2 = expr.getOpNext(); 3791 if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) { 3792 return null; 3793 } 3794 ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals); 3795 while (n1 != null) { 3796 if (n1.getKind() != Kind.Name || n1.getInner() != null) { 3797 return null; 3798 } 3799 grp.elements.add(n1.getName()); 3800 if (n1.getOperation() == null || n1.getOperation() == Operation.Union) { 3801 n1 = n1.getOpNext(); 3802 } else { 3803 return null; 3804 } 3805 } 3806 int total = 0; 3807 for (String n : grp.elements) { 3808 boolean found = false; 3809 for (ElementDefinition child : children) { 3810 String name = tail(child.getPath()); 3811 if (n.equals(name)) { 3812 found = true; 3813 if (!"0".equals(child.getMax())) { 3814 total++; 3815 } 3816 } 3817 } 3818 if (!found) { 3819 return null; 3820 } 3821 } 3822 if (total <= 1) { 3823 return null; 3824 } 3825 return grp; 3826 } 3827 3828 public Set<String> getMasterSourceFileNames() { 3829 return masterSourceFileNames; 3830 } 3831 3832 public void setMasterSourceFileNames(Set<String> masterSourceFileNames) { 3833 this.masterSourceFileNames = masterSourceFileNames; 3834 } 3835 3836 3837 public ProfileKnowledgeProvider getPkp() { 3838 return pkp; 3839 } 3840 3841 3842 public static final String UD_ERROR_STATUS = "error-status"; 3843 public static final int STATUS_OK = 0; 3844 public static final int STATUS_HINT = 1; 3845 public static final int STATUS_WARNING = 2; 3846 public static final int STATUS_ERROR = 3; 3847 public static final int STATUS_FATAL = 4; 3848 private static final String ROW_COLOR_ERROR = "#ffcccc"; 3849 private static final String ROW_COLOR_FATAL = "#ff9999"; 3850 private static final String ROW_COLOR_WARNING = "#ffebcc"; 3851 private static final String ROW_COLOR_HINT = "#ebf5ff"; 3852 private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8"; 3853 3854 public String getRowColor(ElementDefinition element, boolean isConstraintMode) { 3855 switch (element.getUserInt(UD_ERROR_STATUS)) { 3856 case STATUS_HINT: return ROW_COLOR_HINT; 3857 case STATUS_WARNING: return ROW_COLOR_WARNING; 3858 case STATUS_ERROR: return ROW_COLOR_ERROR; 3859 case STATUS_FATAL: return ROW_COLOR_FATAL; 3860 } 3861 if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains(".")) 3862 return null; // ROW_COLOR_NOT_MUST_SUPPORT; 3863 else 3864 return null; 3865 } 3866 3867 public static boolean isExtensionDefinition(StructureDefinition sd) { 3868 return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension"); 3869 } 3870 3871 3872}