
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 lombok.Getter; 048import lombok.Setter; 049import lombok.extern.slf4j.Slf4j; 050import org.hl7.fhir.exceptions.DefinitionException; 051import org.hl7.fhir.exceptions.FHIRException; 052import org.hl7.fhir.exceptions.FHIRFormatError; 053import org.hl7.fhir.r5.conformance.ElementRedirection; 054import org.hl7.fhir.r5.conformance.profile.MappingAssistant.MappingMergeModeOption; 055import org.hl7.fhir.r5.context.IWorkerContext; 056import org.hl7.fhir.r5.elementmodel.ObjectConverter; 057import org.hl7.fhir.r5.elementmodel.Property; 058import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 059import org.hl7.fhir.r5.extensions.ExtensionUtilities; 060import org.hl7.fhir.r5.fhirpath.ExpressionNode; 061import org.hl7.fhir.r5.fhirpath.ExpressionNode.Kind; 062import org.hl7.fhir.r5.fhirpath.ExpressionNode.Operation; 063import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 064import org.hl7.fhir.r5.model.*; 065import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; 066import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBaseComponent; 067import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent; 068import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 069import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; 070import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent; 071import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; 072import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; 073import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 074import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; 075import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 076import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 077import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; 078import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 079import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType; 080import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent; 081import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent; 082import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 083import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; 084import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 085import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 086import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 087import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 088import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 089 090import org.hl7.fhir.r5.utils.UserDataNames; 091import org.hl7.fhir.r5.utils.xver.XVerExtensionManager; 092import org.hl7.fhir.r5.utils.xver.XVerExtensionManager.XVerExtensionStatus; 093import org.hl7.fhir.r5.utils.formats.CSVWriter; 094import org.hl7.fhir.r5.utils.xver.XVerExtensionManagerFactory; 095import org.hl7.fhir.utilities.*; 096import org.hl7.fhir.utilities.i18n.I18nConstants; 097import org.hl7.fhir.utilities.validation.ValidationMessage; 098import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 099import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 100import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 101import org.hl7.fhir.utilities.validation.ValidationOptions; 102import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 103import org.hl7.fhir.utilities.xml.SchematronWriter; 104import org.hl7.fhir.utilities.xml.SchematronWriter.Rule; 105import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType; 106import org.hl7.fhir.utilities.xml.SchematronWriter.Section; 107 108 109/** 110 * This class provides a set of utility operations for working with Profiles. 111 * Key functionality: 112 * * getChildMap --? 113 * * getChildList 114 * * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 115 * * closeDifferential: fill out a differential by excluding anything not mentioned 116 * * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions 117 * * generateTable: generate the HTML for a hierarchical table presentation of a structure 118 * * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point 119 * * summarize: describe the contents of a profile 120 * 121 * 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 122 * 123 * @author Grahame 124 * 125 */ 126@MarkedToMoveToAdjunctPackage 127@Slf4j 128public class ProfileUtilities { 129 130 private static boolean suppressIgnorableExceptions; 131 132 133 public class ElementDefinitionCounter { 134 int countMin = 0; 135 int countMax = 0; 136 int index = 0; 137 ElementDefinition focus; 138 Set<String> names = new HashSet<>(); 139 140 public ElementDefinitionCounter(ElementDefinition ed, int i) { 141 focus = ed; 142 index = i; 143 } 144 145 public int checkMin() { 146 if (countMin > focus.getMin()) { 147 return countMin; 148 } else { 149 return -1; 150 } 151 } 152 153 public int checkMax() { 154 if (countMax > max(focus.getMax())) { 155 return countMax; 156 } else { 157 return -1; 158 } 159 } 160 161 private int max(String max) { 162 if ("*".equals(max)) { 163 return Integer.MAX_VALUE; 164 } else { 165 return Integer.parseInt(max); 166 } 167 } 168 169 public boolean count(ElementDefinition ed, String name) { 170 countMin = countMin + ed.getMin(); 171 if (countMax < Integer.MAX_VALUE) { 172 int m = max(ed.getMax()); 173 if (m == Integer.MAX_VALUE) { 174 countMax = m; 175 } else { 176 countMax = countMax + m; 177 } 178 } 179 boolean ok = !names.contains(name); 180 names.add(name); 181 return ok; 182 } 183 184 public ElementDefinition getFocus() { 185 return focus; 186 } 187 188 public boolean checkMinMax() { 189 return countMin <= countMax; 190 } 191 192 public int getIndex() { 193 return index; 194 } 195 196 } 197 198 public enum AllowUnknownProfile { 199 NONE, // exception if there's any unknown profiles (the default) 200 NON_EXTNEIONS, // don't raise an exception except on Extension (because more is going on there 201 ALL_TYPES // allow any unknow profile 202 } 203 204 /** 205 * These extensions are stripped in inherited profiles (and may be replaced by 206 */ 207 208 public static final List<String> NON_INHERITED_ED_URLS = Arrays.asList( 209 "http://hl7.org/fhir/tools/StructureDefinition/binding-definition", 210 "http://hl7.org/fhir/tools/StructureDefinition/no-binding", 211 "http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding", 212 "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status", 213 "http://hl7.org/fhir/StructureDefinition/structuredefinition-category", 214 "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm", 215 "http://hl7.org/fhir/StructureDefinition/structuredefinition-implements", 216 "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name", 217 "http://hl7.org/fhir/StructureDefinition/structuredefinition-security-category", 218 "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg", 219 "http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version", 220 "http://hl7.org/fhir/tools/StructureDefinition/obligation-profile", 221 "http://hl7.org/fhir/StructureDefinition/obligation-profile", 222 "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status-reason", 223 ExtensionDefinitions.EXT_SUMMARY/*, 224 ExtensionDefinitions.EXT_OBLIGATION_CORE, 225 ExtensionDefinitions.EXT_OBLIGATION_TOOLS*/); 226 227 public static final List<String> DEFAULT_INHERITED_ED_URLS = Arrays.asList( 228 "http://hl7.org/fhir/StructureDefinition/questionnaire-optionRestriction", 229 "http://hl7.org/fhir/StructureDefinition/questionnaire-referenceProfile", 230 "http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource", 231 "http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption", 232 233 "http://hl7.org/fhir/StructureDefinition/mimeType"); 234 235 /** 236 * These extensions are ignored when found in differentials 237 */ 238 public static final List<String> NON_OVERRIDING_ED_URLS = Arrays.asList( 239 "http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable", 240 ExtensionDefinitions.EXT_JSON_NAME, ExtensionDefinitions.EXT_JSON_NAME_DEPRECATED, 241 "http://hl7.org/fhir/tools/StructureDefinition/implied-string-prefix", 242 "http://hl7.org/fhir/tools/StructureDefinition/json-empty-behavior", 243 "http://hl7.org/fhir/tools/StructureDefinition/json-nullable", 244 "http://hl7.org/fhir/tools/StructureDefinition/json-primitive-choice", 245 "http://hl7.org/fhir/tools/StructureDefinition/json-property-key", 246 "http://hl7.org/fhir/tools/StructureDefinition/type-specifier", 247 "http://hl7.org/fhir/tools/StructureDefinition/xml-choice-group", 248 ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED, 249 ExtensionDefinitions.EXT_XML_NAME, ExtensionDefinitions.EXT_XML_NAME_DEPRECATED, 250 "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype" 251 ); 252 253 /** 254 * When these extensions are found, they override whatever is set on the ancestor element 255 */ 256 public static final List<String> OVERRIDING_ED_URLS = Arrays.asList( 257 "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-date-format", 258 ExtensionDefinitions.EXT_DATE_RULES, 259 "http://hl7.org/fhir/StructureDefinition/designNote", 260 "http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits", 261 "http://hl7.org/fhir/StructureDefinition/elementdefinition-question", 262 "http://hl7.org/fhir/StructureDefinition/entryFormat", 263 "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces", 264 "http://hl7.org/fhir/StructureDefinition/maxSize", 265 "http://hl7.org/fhir/StructureDefinition/minLength", 266 "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation", 267 "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", 268 "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden", 269 "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl", 270 "http://hl7.org/fhir/StructureDefinition/questionnaire-signatureRequired", 271 "http://hl7.org/fhir/StructureDefinition/questionnaire-sliderStepValue", 272 "http://hl7.org/fhir/StructureDefinition/questionnaire-supportLink", 273 "http://hl7.org/fhir/StructureDefinition/questionnaire-unit", 274 "http://hl7.org/fhir/StructureDefinition/questionnaire-unitValueSet", 275 "http://hl7.org/fhir/StructureDefinition/questionnaire-usageMode", 276 "http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint", 277 "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name" 278 ); 279 280 public IWorkerContext getContext() { 281 return this.context; 282 } 283 284 public static class SourcedChildDefinitions { 285 private StructureDefinition source; 286 private List<ElementDefinition> list; 287 private String path; 288 public SourcedChildDefinitions(StructureDefinition source, List<ElementDefinition> list) { 289 super(); 290 this.source = source; 291 this.list = list; 292 } 293 public SourcedChildDefinitions(StructureDefinition source, List<ElementDefinition> list, String path) { 294 super(); 295 this.source = source; 296 this.list = list; 297 this.path = path; 298 } 299 public StructureDefinition getSource() { 300 return source; 301 } 302 public List<ElementDefinition> getList() { 303 return list; 304 } 305 public String getPath() { 306 return path; 307 } 308 309 } 310 311 public class ElementDefinitionResolution { 312 313 private StructureDefinition source; 314 private ElementDefinition element; 315 316 public ElementDefinitionResolution(StructureDefinition source, ElementDefinition element) { 317 this.source = source; 318 this.element = element; 319 } 320 321 public StructureDefinition getSource() { 322 return source; 323 } 324 325 public ElementDefinition getElement() { 326 return element; 327 } 328 329 } 330 331 public static class ElementChoiceGroup { 332 private Row row; 333 private String name; 334 private boolean mandatory; 335 private List<String> elements = new ArrayList<>(); 336 337 public ElementChoiceGroup(String name, boolean mandatory) { 338 super(); 339 this.name = name; 340 this.mandatory = mandatory; 341 } 342 public Row getRow() { 343 return row; 344 } 345 public List<String> getElements() { 346 return elements; 347 } 348 public void setRow(Row row) { 349 this.row = row; 350 } 351 public String getName() { 352 return name; 353 } 354 public boolean isMandatory() { 355 return mandatory; 356 } 357 public void setMandatory(boolean mandatory) { 358 this.mandatory = mandatory; 359 } 360 361 } 362 363 private static final int MAX_RECURSION_LIMIT = 10; 364 365 public static class ExtensionContext { 366 367 private ElementDefinition element; 368 private StructureDefinition defn; 369 370 public ExtensionContext(StructureDefinition ext, ElementDefinition ed) { 371 this.defn = ext; 372 this.element = ed; 373 } 374 375 public ElementDefinition getElement() { 376 return element; 377 } 378 379 public StructureDefinition getDefn() { 380 return defn; 381 } 382 383 public String getUrl() { 384 if (element == defn.getSnapshot().getElement().get(0)) 385 return defn.getUrl(); 386 else 387 return element.getSliceName(); 388 } 389 390 public ElementDefinition getExtensionValueDefinition() { 391 int i = defn.getSnapshot().getElement().indexOf(element)+1; 392 while (i < defn.getSnapshot().getElement().size()) { 393 ElementDefinition ed = defn.getSnapshot().getElement().get(i); 394 if (ed.getPath().equals(element.getPath())) 395 return null; 396 if (ed.getPath().startsWith(element.getPath()+".value") && !ed.hasSlicing()) 397 return ed; 398 i++; 399 } 400 return null; 401 } 402 } 403 404 private static final boolean COPY_BINDING_EXTENSIONS = false; 405 private static final boolean DONT_DO_THIS = false; 406 407 private boolean debug; 408 // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here 409 private final IWorkerContext context; 410 private FHIRPathEngine fpe; 411 private List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); 412 private List<String> snapshotStack = new ArrayList<String>(); 413 private ProfileKnowledgeProvider pkp; 414// private boolean igmode; 415 private ValidationOptions terminologyServiceOptions = new ValidationOptions(FhirPublication.R5); 416 private boolean newSlicingProcessing; 417 private String defWebRoot; 418 private boolean autoFixSliceNames; 419 private XVerExtensionManager xver; 420 private boolean wantFixDifferentialFirstElementType; 421 private Set<String> masterSourceFileNames; 422 private Set<String> localFileNames; 423 private Map<String, SourcedChildDefinitions> childMapCache = new HashMap<>(); 424 private AllowUnknownProfile allowUnknownProfile = AllowUnknownProfile.ALL_TYPES; 425 private MappingMergeModeOption mappingMergeMode = MappingMergeModeOption.APPEND; 426 private boolean forPublication; 427 private List<StructureDefinition> obligationProfiles = new ArrayList<>(); 428 private boolean wantThrowExceptions; 429 private List<String> suppressedMappings= new ArrayList<>(); 430 @Getter @Setter private Parameters parameters; 431 432 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) { 433 super(); 434 this.context = context; 435 if (messages != null) { 436 this.messages = messages; 437 } else { 438 wantThrowExceptions = true; 439 } 440 this.pkp = pkp; 441 442 this.fpe = fpe; 443 if (context != null && this.fpe == null) { 444 this.fpe = new FHIRPathEngine(context, this); 445 } 446 if (context != null) { 447 parameters = context.getExpansionParameters(); 448 } 449 } 450 451 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) { 452 super(); 453 this.context = context; 454 if (messages != null) { 455 this.messages = messages; 456 } else { 457 wantThrowExceptions = true; 458 } 459 this.pkp = pkp; 460 if (context != null) { 461 this.fpe = new FHIRPathEngine(context, this); 462 } 463 464 if (context != null) { 465 parameters = context.getExpansionParameters(); 466 } 467 } 468 469 public boolean isWantFixDifferentialFirstElementType() { 470 return wantFixDifferentialFirstElementType; 471 } 472 473 public void setWantFixDifferentialFirstElementType(boolean wantFixDifferentialFirstElementType) { 474 this.wantFixDifferentialFirstElementType = wantFixDifferentialFirstElementType; 475 } 476 477 public boolean isAutoFixSliceNames() { 478 return autoFixSliceNames; 479 } 480 481 public ProfileUtilities setAutoFixSliceNames(boolean autoFixSliceNames) { 482 this.autoFixSliceNames = autoFixSliceNames; 483 return this; 484 } 485 486 public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element, boolean chaseTypes) throws DefinitionException { 487 return getChildMap(profile, element, chaseTypes, null); 488 } 489 public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element, boolean chaseTypes, String type) throws DefinitionException { 490 String cacheKey = "cm."+profile.getVersionedUrl()+"#"+(element.hasId() ? element.getId() : element.getPath())+"."+chaseTypes; 491 if (childMapCache.containsKey(cacheKey)) { 492 return childMapCache.get(cacheKey); 493 } 494 StructureDefinition src = profile; 495 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 496 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 497 int iOffs = elements.indexOf(element) + 1; 498 boolean walksIntoElement = elements.size() > iOffs && elements.get(iOffs).getPath().startsWith(element.getPath()); 499 if (element.getContentReference() != null && !walksIntoElement) { 500 List<ElementDefinition> list = null; 501 String id = null; 502 if (element.getContentReference().startsWith("#")) { 503 // internal reference 504 id = element.getContentReference().substring(1); 505 list = profile.getSnapshot().getElement(); 506 } else if (element.getContentReference().contains("#")) { 507 // external reference 508 String ref = element.getContentReference(); 509 StructureDefinition sd = findProfile(ref.substring(0, ref.indexOf("#")), profile); 510 if (sd == null) { 511 throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'"); 512 } 513 src = sd; 514 list = sd.getSnapshot().getElement(); 515 id = ref.substring(ref.indexOf("#")+1); 516 } else { 517 throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'"); 518 } 519 520 for (ElementDefinition e : list) { 521 if (id.equals(e.getId())) 522 return getChildMap(src, e, true); 523 } 524 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_NAME_REFERENCE__AT_PATH_, element.getContentReference(), element.getPath())); 525 526 } else { 527 String path = element.getPath(); 528 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 529 ElementDefinition e = elements.get(index); 530 if (e.getPath().startsWith(path + ".")) { 531 // We only want direct children, not all descendants 532 if (!e.getPath().substring(path.length()+1).contains(".")) 533 res.add(e); 534 } else 535 break; 536 } 537 if (res.isEmpty() && chaseTypes) { 538 // we've got no in-line children. Some consumers of this routine will figure this out for themselves but most just want to walk into 539 // the type children. 540 src = null; 541 if (type != null) { 542 src = context.fetchTypeDefinition(type); 543 } else if (element.getType().isEmpty()) { 544 throw new DefinitionException("No defined children and no type information on element '"+element.getId()+"'"); 545 } else if (element.getType().size() > 1) { 546 // this is a problem. There's two separate but related issues 547 // the first is what's going on here - the profile has walked into an element without fixing the type 548 // this might be ok - maybe it's just going to constrain extensions for all types, though this is generally a bad idea 549 // but if that's all it's doing, we'll just pretend we have an element. Only, it's not really an element so that might 550 // blow up on us later in mystifying ways. We'll have to wear it though, because there's profiles out there that do this 551 // the second problem is whether this should be some common descendent of Element - I'm not clear about that 552 // left as a problem for the future. 553 // 554 // this is what the code was prior to 2025-08-27: 555 // throw new DefinitionException("No defined children and multiple possible types '"+element.typeSummary()+"' on element '"+element.getId()+"'"); 556 src = context.fetchTypeDefinition("Element"); 557 } else if (element.getType().get(0).getProfile().size() > 1) { 558 throw new DefinitionException("No defined children and multiple possible type profiles '"+element.typeSummary()+"' on element '"+element.getId()+"'"); 559 } else if (element.getType().get(0).hasProfile()) { 560 src = findProfile(element.getType().get(0).getProfile().get(0).getValue(), profile); 561 if (src == null) { 562 throw new DefinitionException("No defined children and unknown type profile '"+element.typeSummary()+"' on element '"+element.getId()+"'"); 563 } 564 } else { 565 src = context.fetchTypeDefinition(element.getType().get(0).getWorkingCode()); 566 if (src == null) { 567 throw new DefinitionException("No defined children and unknown type '"+element.typeSummary()+"' on element '"+element.getId()+"'"); 568 } 569 } 570 SourcedChildDefinitions scd = getChildMap(src, src.getSnapshot().getElementFirstRep(), false); 571 res = scd.list; 572 } 573 SourcedChildDefinitions result = new SourcedChildDefinitions(src, res); 574 childMapCache.put(cacheKey, result); 575 return result; 576 } 577 } 578 579 580 public List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException { 581 if (!element.hasSlicing()) 582 throw new Error(context.formatMessage(I18nConstants.GETSLICELIST_SHOULD_ONLY_BE_CALLED_WHEN_THE_ELEMENT_HAS_SLICING)); 583 584 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 585 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 586 String path = element.getPath(); 587 int start = findElementIndex(elements, element); 588 for (int index = start + 1; index < elements.size(); index++) { 589 ElementDefinition e = elements.get(index); 590 if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) { 591 // We want elements with the same path (until we hit an element that doesn't start with the same path) 592 if (e.getPath().equals(element.getPath())) 593 res.add(e); 594 } else 595 break; 596 } 597 return res; 598 } 599 600 601 private int findElementIndex(List<ElementDefinition> elements, ElementDefinition element) { 602 int res = elements.indexOf(element); 603 if (res == -1) { 604 for (int i = 0; i < elements.size(); i++) { 605 Element t = elements.get(i); 606 if (t.getId().equals(element.getId())) { 607 res = i; 608 } 609 } 610 } 611 return res; 612 } 613 614 /** 615 * Given a Structure, navigate to the element given by the path and return the direct children of that element 616 * 617 * @param profile The structure to navigate into 618 * @param path The path of the element within the structure to get the children for 619 * @return A List containing the element children (all of them are Elements) 620 */ 621 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) { 622 return getChildList(profile, path, id, false); 623 } 624 625 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff) { 626 return getChildList(profile, path, id, diff, false); 627 } 628 629 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff, boolean refs) { 630 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 631 632 boolean capturing = id==null; 633 if (id==null && !path.contains(".")) 634 capturing = true; 635 636 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 637 for (ElementDefinition e : list) { 638 if (e == null) 639 throw new Error(context.formatMessage(I18nConstants.ELEMENT__NULL_, profile.getUrl())); 640// if (e.getId() == null) // this is sort of true, but in some corner cases it's not, and in those cases, we don't care 641// throw new Error(context.formatMessage(I18nConstants.ELEMENT_ID__NULL__ON_, e.toString(), profile.getUrl())); 642 643 if (!capturing && id!=null && e.getId().equals(id)) { 644 capturing = true; 645 } 646 647 // If our element is a slice, stop capturing children as soon as we see the next slice 648 if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path)) 649 break; 650 651 if (capturing) { 652 String p = e.getPath(); 653 654 if (refs && !Utilities.noString(e.getContentReference()) && path.startsWith(p)) { 655 if (path.length() > p.length()) { 656 return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff); 657 } else if (e.getContentReference().startsWith("#")) { 658 return getChildList(profile, e.getContentReference().substring(1), null, diff); 659 } else if (e.getContentReference().contains("#")) { 660 String url = e.getContentReference().substring(0, e.getContentReference().indexOf("#")); 661 StructureDefinition sd = findProfile(url, profile); 662 if (sd == null) { 663 throw new DefinitionException("Unable to find Structure "+url); 664 } 665 return getChildList(sd, e.getContentReference().substring(e.getContentReference().indexOf("#")+1), null, diff); 666 } else { 667 return getChildList(profile, e.getContentReference(), null, diff); 668 } 669 670 } else if (p.startsWith(path+".") && !p.equals(path)) { 671 String tail = p.substring(path.length()+1); 672 if (!tail.contains(".")) { 673 res.add(e); 674 } 675 } 676 } 677 } 678 679 return res; 680 } 681 682 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff, boolean refs) { 683 return getChildList(structure, element.getPath(), element.getId(), diff, refs); 684 } 685 686 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) { 687 return getChildList(structure, element.getPath(), element.getId(), diff); 688 } 689 690 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) { 691 if (element.hasContentReference()) { 692 ElementDefinition target = element; 693 for (ElementDefinition t : structure.getSnapshot().getElement()) { 694 if (t.getId().equals(element.getContentReference().substring(1))) { 695 target = t; 696 } 697 } 698 return getChildList(structure, target.getPath(), target.getId(), false); 699 } else { 700 return getChildList(structure, element.getPath(), element.getId(), false); 701 } 702 } 703 704 /** 705 * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 706 * 707 * @param base - the base structure on which the differential will be applied 708 * @param derived - the differential to apply to the base 709 * @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) 710 * @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) 711 * @return 712 * @throws FHIRException 713 * @throws DefinitionException 714 * @throws Exception 715 */ 716 public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException { 717 if (base == null) { 718 throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED)); 719 } 720 if (derived == null) { 721 throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED)); 722 } 723 checkNotGenerating(base, "Base for generating a snapshot for the profile "+derived.getUrl()); 724 checkNotGenerating(derived, "Focus for generating a snapshot"); 725 726 if (!base.hasType()) { 727 throw new DefinitionException(context.formatMessage(I18nConstants.BASE_PROFILE__HAS_NO_TYPE, base.getUrl())); 728 } 729 if (!derived.hasType()) { 730 throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_TYPE, derived.getUrl())); 731 } 732 if (!derived.hasDerivation()) { 733 throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_DERIVATION_VALUE_AND_SO_CANT_BE_PROCESSED, derived.getUrl())); 734 } 735 if (!base.getType().equals(derived.getType()) && derived.getDerivation() == TypeDerivationRule.CONSTRAINT) { 736 throw new DefinitionException(context.formatMessage(I18nConstants.BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___, base.getUrl(), base.getType(), derived.getUrl(), derived.getType())); 737 } 738 if (!base.hasSnapshot()) { 739 StructureDefinition sdb = findProfile(base.getBaseDefinition(), base); 740 if (sdb == null) 741 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE__FOR_, base.getBaseDefinition(), base.getUrl())); 742 checkNotGenerating(sdb, "an extension base"); 743 generateSnapshot(sdb, base, base.getUrl(), (sdb.hasWebPath()) ? Utilities.extractBaseUrl(sdb.getWebPath()) : webUrl, base.getName()); 744 } 745 fixTypeOfResourceId(base); 746 if (base.hasExtension(ExtensionDefinitions.EXT_TYPE_PARAMETER)) { 747 checkTypeParameters(base, derived); 748 } 749 750 if (snapshotStack.contains(derived.getUrl())) { 751 throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString())); 752 } 753 derived.setGeneratingSnapshot(true); 754 snapshotStack.add(derived.getUrl()); 755 boolean oldCopyUserData = Base.isCopyUserData(); 756 Base.setCopyUserData(true); 757 try { 758 759 if (!Utilities.noString(webUrl) && !webUrl.endsWith("/")) 760 webUrl = webUrl + '/'; 761 762 if (defWebRoot == null) 763 defWebRoot = webUrl; 764 derived.setSnapshot(new StructureDefinitionSnapshotComponent()); 765 766 try { 767 checkDifferential(derived.getDifferential().getElement(), derived.getTypeName(), derived.getUrl()); 768 checkDifferentialBaseType(derived); 769 770 log.debug("Differential: "); 771 int debugPadding = 0; 772 for (ElementDefinition ed : derived.getDifferential().getElement()) { 773 log.debug(" "+Utilities.padLeft(Integer.toString(debugPadding), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 774 debugPadding++; 775 } 776 log.debug("Snapshot: "); 777 debugPadding = 0; 778 for (ElementDefinition ed : base.getSnapshot().getElement()) { 779 log.debug(" "+Utilities.padLeft(Integer.toString(debugPadding), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 780 debugPadding++; 781 } 782 783 784 copyInheritedExtensions(base, derived, webUrl); 785 786 findInheritedObligationProfiles(derived); 787 // so we have two lists - the base list, and the differential list 788 // the differential list is only allowed to include things that are in the base list, but 789 // is allowed to include them multiple times - thereby slicing them 790 791 // our approach is to walk through the base list, and see whether the differential 792 // says anything about them. 793 // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths 794 795 796 for (ElementDefinition e : derived.getDifferential().getElement()) 797 e.clearUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT); 798 799 // we actually delegate the work to a subroutine so we can re-enter it with a different cursors 800 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 801 new SnapshotGenerationPreProcessor(this).process(diff, derived); 802 803 StructureDefinitionSnapshotComponent baseSnapshot = base.getSnapshot(); 804 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 805 String derivedType = derived.getTypeName(); 806 807 baseSnapshot = cloneSnapshot(baseSnapshot, base.getTypeName(), derivedType); 808 } 809 // if (derived.getId().equals("2.16.840.1.113883.10.20.22.2.1.1")) { 810 // debug = true; 811 // } 812 813 MappingAssistant mappingDetails = new MappingAssistant(mappingMergeMode, base, derived, context.getVersion(), suppressedMappings); 814 815 ProfilePathProcessor.processPaths(this, base, derived, url, webUrl, diff, baseSnapshot, mappingDetails); 816 817 checkGroupConstraints(derived); 818 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 819 int i = 0; 820 for (ElementDefinition e : diff.getElement()) { 821 if (!e.hasUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT) && e.getPath().contains(".")) { 822 ElementDefinition existing = getElementInCurrentContext(e.getPath(), derived.getSnapshot().getElement()); 823 if (existing != null) { 824 updateFromDefinition(existing, e, profileName, false, url, base, derived, "StructureDefinition.differential.element["+i+"]", mappingDetails, false); 825 } else { 826 ElementDefinition outcome = updateURLs(url, webUrl, e.copy(), true); 827 e.setUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT, outcome); 828 markExtensions(outcome, true, derived); 829 derived.getSnapshot().addElement(outcome); 830 if (walksInto(diff.getElement(), e)) { 831 if (e.getType().size() > 1) { 832 throw new DefinitionException("Unsupported scenario: specialization walks into multiple types at "+e.getId()); 833 } else { 834 addInheritedElementsForSpecialization(derived.getSnapshot(), outcome, outcome.getTypeFirstRep().getWorkingCode(), outcome.getPath(), url, webUrl); 835 } 836 } 837 } 838 } 839 i++; 840 } 841 } 842 843 for (int i = 0; i < derived.getSnapshot().getElement().size(); i++) { 844 ElementDefinition ed = derived.getSnapshot().getElement().get(i); 845 if (ed.getType().size() > 1) { 846 List<TypeRefComponent> toRemove = new ArrayList<ElementDefinition.TypeRefComponent>(); 847 for (TypeRefComponent tr : ed.getType()) { 848 ElementDefinition typeSlice = findTypeSlice(derived.getSnapshot().getElement(), i, ed.getPath(), tr.getWorkingCode()); 849 if (typeSlice != null && typeSlice.prohibited()) { 850 toRemove.add(tr); 851 } 852 } 853 ed.getType().removeAll(toRemove); 854 } 855 } 856 if (derived.getKind() != StructureDefinitionKind.LOGICAL && !derived.getSnapshot().getElementFirstRep().getType().isEmpty()) 857 throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl())); 858 mappingDetails.update(); 859 860 setIds(derived, false); 861 862 log.debug("Differential: "); 863 int debugPad = 0; 864 for (ElementDefinition ed : derived.getDifferential().getElement()) { 865 log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)); 866 debugPad++; 867 } 868 log.debug("Diff (processed): "); 869 debugPad = 0; 870 for (ElementDefinition ed : diff.getElement()) { 871 log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+ 872 " -> "+(destInfo(ed, derived.getSnapshot().getElement()))); 873 debugPad++; 874 } 875 log.debug("Snapshot: "); 876 debugPad = 0; 877 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 878 log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 879 debugPad++; 880 } 881 882 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 883 //Check that all differential elements have a corresponding snapshot element 884 int ce = 0; 885 int i = 0; 886 for (ElementDefinition e : diff.getElement()) { 887 if (!e.hasUserData(UserDataNames.SNAPSHOT_diff_source)) { 888 // was injected during preprocessing - this is ok 889 } else { 890 if (e.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS)) 891 ((Base) e.getUserData(UserDataNames.SNAPSHOT_diff_source)).setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, e.getUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS)); 892 if (e.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER)) 893 ((Base) e.getUserData(UserDataNames.SNAPSHOT_diff_source)).setUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER, e.getUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER)); 894 } 895 if (!e.hasUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT)) { 896 b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath()); 897 ce++; 898 if (e.hasId()) { 899 String msg = "No match found for "+e.getId()+" in the generated snapshot: check that the path and definitions are legal in the differential (including order)"; 900 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, "StructureDefinition.differential.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR)); 901 } 902 } else { 903 ElementDefinition sed = (ElementDefinition) e.getUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT); 904 sed.setUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF, e); // note: this means diff/snapshot are cross-linked 905 } 906 i++; 907 } 908 if (!Utilities.noString(b.toString())) { 909 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)"; 910 911 log.debug("Error in snapshot generation: "+msg); 912 913 log.debug("Differential: "); 914 for (ElementDefinition ed : derived.getDifferential().getElement()) 915 log.debug(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 916 log.debug("Snapshot: "); 917 for (ElementDefinition ed : derived.getSnapshot().getElement()) 918 log.debug(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 919 920 921 handleError(url, msg); 922 } 923 // hack around a problem in R4 definitions (somewhere?) 924 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 925 for (ElementDefinitionMappingComponent mm : ed.getMapping()) { 926 if (mm.hasMap()) { 927 mm.setMap(mm.getMap().trim()); 928 } 929 } 930 for (ElementDefinitionConstraintComponent s : ed.getConstraint()) { 931 if (s.hasSource()) { 932 String ref = s.getSource(); 933 if (!Utilities.isAbsoluteUrl(ref)) { 934 if (ref.contains(".")) { 935 s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref.substring(0, ref.indexOf("."))+"#"+ref); 936 } else { 937 s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref); 938 } 939 } 940 } 941 } 942 } 943 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 944 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 945 if (!ed.hasBase()) { 946 ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax()); 947 } 948 } 949 } 950 // check slicing is ok while we're at it. and while we're doing this. update the minimum count if we need to 951 String tn = derived.getType(); 952 if (tn.contains("/")) { 953 tn = tn.substring(tn.lastIndexOf("/")+1); 954 } 955 Map<String, ElementDefinitionCounter> slices = new HashMap<>(); 956 i = 0; 957 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 958 if (ed.hasSlicing()) { 959 slices.put(ed.getPath(), new ElementDefinitionCounter(ed, i)); 960 } else { 961 Set<String> toRemove = new HashSet<>(); 962 for (String s : slices.keySet()) { 963 if (Utilities.charCount(s, '.') >= Utilities.charCount(ed.getPath(), '.') && !s.equals(ed.getPath())) { 964 toRemove.add(s); 965 } 966 } 967 for (String s : toRemove) { 968 ElementDefinitionCounter slice = slices.get(s); 969 int count = slice.checkMin(); 970 boolean repeats = !"1".equals(slice.getFocus().getBase().getMax()); // type slicing if repeats = 1 971 if (count > -1 && repeats) { 972 if (slice.getFocus().hasUserData(UserDataNames.SNAPSHOT_auto_added_slicing)) { 973 slice.getFocus().setMin(count); 974 } else { 975 String msg = "The slice definition for "+slice.getFocus().getId()+" has a minimum of "+slice.getFocus().getMin()+" but the slices add up to a minimum of "+count; 976 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 977 "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION).setIgnorableError(true)); 978 } 979 } 980 count = slice.checkMax(); 981 if (count > -1 && repeats) { 982 String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" but the slices add up to a maximum of "+count+". Check that this is what is intended"; 983 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 984 "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.INFORMATION)); 985 } 986 if (!slice.checkMinMax()) { 987 String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" which is less than the minimum of "+slice.getFocus().getMin(); 988 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 989 "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.WARNING)); 990 } 991 slices.remove(s); 992 } 993 } 994 if (ed.getPath().contains(".") && !ed.getPath().startsWith(tn+".")) { 995 throw new Error("The element "+ed.getId()+" in the profile '"+derived.getVersionedUrl()+" doesn't have the right path (should start with "+tn+"."); 996 } 997 if (ed.hasSliceName() && !slices.containsKey(ed.getPath())) { 998 String msg = "The element "+ed.getId()+" launches straight into slicing without the slicing being set up properly first"; 999 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 1000 "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); 1001 } 1002 if (ed.hasSliceName() && slices.containsKey(ed.getPath())) { 1003 if (!slices.get(ed.getPath()).count(ed, ed.getSliceName())) { 1004 String msg = "Duplicate slice name "+ed.getSliceName()+" on "+ed.getId()+" (["+i+"])"; 1005 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 1006 "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); 1007 } 1008 } 1009 i++; 1010 } 1011 1012 i = 0; 1013 // last, check for wrong profiles or target profiles, or unlabeled extensions 1014 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 1015 for (TypeRefComponent t : ed.getType()) { 1016 for (UriType u : t.getProfile()) { 1017 StructureDefinition sd = findProfile(u.getValue(), derived); 1018 if (sd == null) { 1019 if (makeXVer().matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) { 1020 sd = xver.getDefinition(u.getValue()); 1021 } 1022 } 1023 if (sd == null) { 1024 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 1025 "StructureDefinition.snapshot.element["+i+"]", "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING)); 1026 } else { 1027 String wt = t.getWorkingCode(); 1028 if (ed.getPath().equals("Bundle.entry.response.outcome")) { 1029 wt = "OperationOutcome"; 1030 } 1031 String tt = sd.getType(); 1032 boolean elementProfile = u.hasExtension(ExtensionDefinitions.EXT_PROFILE_ELEMENT); 1033 if (elementProfile) { 1034 ElementDefinition edt = sd.getSnapshot().getElementById(u.getExtensionString(ExtensionDefinitions.EXT_PROFILE_ELEMENT)); 1035 if (edt == null) { 1036 handleError(url, "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt); 1037 } else { 1038 tt = edt.typeSummary(); 1039 } 1040 } 1041 if (!tt.equals(wt)) { 1042 boolean ok = !elementProfile && isCompatibleType(wt, sd); 1043 if (!ok) { 1044 handleError(url, "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt); 1045 } 1046 } 1047 } 1048 } 1049 } 1050 i++; 1051 } 1052 } catch (Exception e) { 1053 log.error("Exception generating snapshot for "+derived.getVersionedUrl()+": " +e.getMessage()); 1054 log.debug(e.getMessage(), e); 1055 // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind 1056 derived.setSnapshot(null); 1057 derived.setGeneratingSnapshot(false); 1058 throw e; 1059 } 1060 } finally { 1061 Base.setCopyUserData(oldCopyUserData); 1062 derived.setGeneratingSnapshot(false); 1063 snapshotStack.remove(derived.getUrl()); 1064 } 1065 if (base.getVersion() != null) { 1066 derived.getSnapshot().addExtension(ExtensionDefinitions.EXT_VERSION_BASE, new StringType(base.getVersion())); 1067 } 1068 derived.setGeneratedSnapshot(true); 1069 //derived.setUserData(UserDataNames.SNAPSHOT_GENERATED, true); // used by the publisher 1070 derived.setUserData(UserDataNames.SNAPSHOT_GENERATED_MESSAGES, messages); // used by the publisher 1071 } 1072 1073 1074 private String destInfo(ElementDefinition ed, List<ElementDefinition> snapshot) { 1075 ElementDefinition sed = (ElementDefinition) ed.getUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT); 1076 if (sed == null) { 1077 return "(null)"; 1078 } else { 1079 int index = snapshot.indexOf(sed); 1080 return ""+index+" "+sed.getId(); 1081 } 1082 } 1083 1084 private ElementDefinition findTypeSlice(List<ElementDefinition> list, int i, String path, String typeCode) { 1085 for (int j = i+1; j < list.size(); j++) { 1086 ElementDefinition ed = list.get(j); 1087 if (pathMatches(path, ed) && typeMatches(ed, typeCode)) { 1088 return ed; 1089 } 1090 } 1091 return null; 1092 } 1093 1094 private boolean pathMatches(String path, ElementDefinition ed) { 1095 String p = ed.getPath(); 1096 if (path.equals(p)) { 1097 return true; 1098 } 1099 if (path.endsWith("[x]")) { // it should 1100 path = path.substring(0, path.length()-3); 1101 if (p.startsWith(path) && p.length() > path.length() && !p.substring(path.length()).contains(".")) { 1102 return true; 1103 } 1104 } 1105 return false; 1106 } 1107 1108 private boolean typeMatches(ElementDefinition ed, String typeCode) { 1109 return ed.getType().size() == 1 && typeCode.equals(ed.getTypeFirstRep().getWorkingCode()); 1110 } 1111 1112 private void checkTypeParameters(StructureDefinition base, StructureDefinition derived) { 1113 String bt = ExtensionUtilities.readStringSubExtension(base, ExtensionDefinitions.EXT_TYPE_PARAMETER, "type"); 1114 if (!derived.hasExtension(ExtensionDefinitions.EXT_TYPE_PARAMETER)) { 1115 throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_MISSING, base.getVersionedUrl(), bt, derived.getVersionedUrl())); 1116 } 1117 String dt = ExtensionUtilities.readStringSubExtension(derived, ExtensionDefinitions.EXT_TYPE_PARAMETER, "type"); 1118 StructureDefinition bsd = context.fetchTypeDefinition(bt); 1119 StructureDefinition dsd = context.fetchTypeDefinition(dt); 1120 if (bsd == null) { 1121 throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, base.getVersionedUrl(), bt)); 1122 } 1123 if (dsd == null) { 1124 throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, derived.getVersionedUrl(), dt)); 1125 } 1126 StructureDefinition t = dsd; 1127 while (t != bsd && t != null) { 1128 t = findProfile(t.getBaseDefinition(), t); 1129 } 1130 if (t == null) { 1131 throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_INVALID, base.getVersionedUrl(), bt, derived.getVersionedUrl(), dt)); 1132 } 1133 } 1134 1135 private XVerExtensionManager makeXVer() { 1136 if (xver == null) { 1137 xver = XVerExtensionManagerFactory.createExtensionManager(context); 1138 } 1139 return xver; 1140 } 1141 1142 private ElementDefinition getElementInCurrentContext(String path, List<ElementDefinition> list) { 1143 for (int i = list.size() -1; i >= 0; i--) { 1144 ElementDefinition t = list.get(i); 1145 if (t.getPath().equals(path)) { 1146 return t; 1147 } else if (!path.startsWith(head(t.getPath()))) { 1148 return null; 1149 } 1150 } 1151 return null; 1152 } 1153 1154 private String head(String path) { 1155 return path.contains(".") ? path.substring(0, path.lastIndexOf(".")+1) : path; 1156 } 1157 1158 private void findInheritedObligationProfiles(StructureDefinition derived) { 1159 List<Extension> list = derived.getExtensionsByUrl(ExtensionDefinitions.EXT_OBLIGATION_INHERITS_NEW, ExtensionDefinitions.EXT_OBLIGATION_INHERITS_OLD); 1160 for (Extension ext : list) { 1161 StructureDefinition op = findProfile(ext.getValueCanonicalType().primitiveValue(), derived); 1162 if (op != null && ExtensionUtilities.readBoolExtension(op, ExtensionDefinitions.EXT_OBLIGATION_PROFILE_FLAG_NEW, ExtensionDefinitions.EXT_OBLIGATION_PROFILE_FLAG_OLD)) { 1163 if (derived.getBaseDefinitionNoVersion().equals(op.getBaseDefinitionNoVersion())) { 1164 obligationProfiles.add(op); 1165 } 1166 } 1167 } 1168 } 1169 1170 private void handleError(String url, String msg) { 1171 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, msg, ValidationMessage.IssueSeverity.ERROR)); 1172 } 1173 1174 private void addMessage(ValidationMessage msg) { 1175 messages.add(msg); 1176 if (msg.getLevel() == IssueSeverity.ERROR && wantThrowExceptions) { 1177 throw new DefinitionException(msg.getMessage()); 1178 } 1179 } 1180 1181 private void copyInheritedExtensions(StructureDefinition base, StructureDefinition derived, String webUrl) { 1182 for (Extension ext : base.getExtension()) { 1183 if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS)) { 1184 String action = getExtensionAction(ext.getUrl()); 1185 if (!"ignore".equals(action)) { 1186 boolean exists = derived.hasExtension(ext.getUrl()); 1187 if ("add".equals(action) || !exists) { 1188 Extension next = ext.copy(); 1189 if (next.hasValueMarkdownType()) { 1190 MarkdownType md = next.getValueMarkdownType(); 1191 md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false)); 1192 } 1193 derived.getExtension().add(next); 1194 } else if ("overwrite".equals(action)) { 1195 Extension oext = derived.getExtensionByUrl(ext.getUrl()); 1196 Extension next = ext.copy(); 1197 if (next.hasValueMarkdownType()) { 1198 MarkdownType md = next.getValueMarkdownType(); 1199 md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false)); 1200 } 1201 oext.setValue(next.getValue()); 1202 } 1203 } 1204 } 1205 } 1206 } 1207 1208 private String getExtensionAction(String url) { 1209 StructureDefinition sd = context.fetchResourceRaw(StructureDefinition.class, url); 1210 if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_SNAPSHOT_BEHAVIOR)) { 1211 return ExtensionUtilities.readStringExtension(sd, ExtensionDefinitions.EXT_SNAPSHOT_BEHAVIOR); 1212 } 1213 return "defer"; 1214 } 1215 1216 private void addInheritedElementsForSpecialization(StructureDefinitionSnapshotComponent snapshot, ElementDefinition focus, String type, String path, String url, String weburl) { 1217 StructureDefinition sd = context.fetchTypeDefinition(type); 1218 if (sd != null) { 1219 // don't do this. should already be in snapshot ... addInheritedElementsForSpecialization(snapshot, focus, sd.getBaseDefinition(), path, url, weburl); 1220 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 1221 if (ed.getPath().contains(".")) { 1222 ElementDefinition outcome = updateURLs(url, weburl, ed.copy(), true); 1223 outcome.setPath(outcome.getPath().replace(sd.getTypeName(), path)); 1224 markExtensions(outcome, false, sd); 1225 snapshot.getElement().add(outcome); 1226 } else { 1227 focus.getConstraint().addAll(ed.getConstraint()); 1228 for (Extension ext : ed.getExtension()) { 1229 if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) && !focus.hasExtension(ext.getUrl())) { 1230 focus.getExtension().add(markExtensionSource(ext.copy(), false, sd)); 1231 } 1232 } 1233 } 1234 } 1235 } 1236 } 1237 1238 private boolean walksInto(List<ElementDefinition> list, ElementDefinition ed) { 1239 int i = list.indexOf(ed); 1240 return (i < list.size() - 1) && list.get(i + 1).getPath().startsWith(ed.getPath()+"."); 1241 } 1242 1243 private void fixTypeOfResourceId(StructureDefinition base) { 1244 if (base.getKind() == StructureDefinitionKind.RESOURCE && (base.getFhirVersion() == null || VersionUtilities.isR4Plus(base.getFhirVersion().toCode()))) { 1245 fixTypeOfResourceId(base.getSnapshot().getElement()); 1246 fixTypeOfResourceId(base.getDifferential().getElement()); 1247 } 1248 } 1249 1250 private void fixTypeOfResourceId(List<ElementDefinition> list) { 1251 for (ElementDefinition ed : list) { 1252 if (ed.hasBase() && ed.getBase().getPath().equals("Resource.id")) { 1253 for (TypeRefComponent tr : ed.getType()) { 1254 tr.setCode("http://hl7.org/fhirpath/System.String"); 1255 tr.removeExtension(ExtensionDefinitions.EXT_FHIR_TYPE); 1256 ExtensionUtilities.addUrlExtension(tr, ExtensionDefinitions.EXT_FHIR_TYPE, "id"); 1257 } 1258 } 1259 } 1260 } 1261 1262 /** 1263 * Check if derived has the correct base type 1264 * 1265 * Clear first element of differential under certain conditions. 1266 * 1267 * @param derived 1268 * @throws Error 1269 */ 1270 private void checkDifferentialBaseType(StructureDefinition derived) throws Error { 1271 if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) { 1272 if (wantFixDifferentialFirstElementType && typeMatchesAncestor(derived.getDifferential().getElementFirstRep().getType(), derived.getBaseDefinitionNoVersion(), derived)) { 1273 derived.getDifferential().getElementFirstRep().getType().clear(); 1274 } else if (derived.getKind() != StructureDefinitionKind.LOGICAL) { 1275 throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_DIFFERENTIAL_ELEMENT)); 1276 } 1277 } 1278 } 1279 1280 private boolean typeMatchesAncestor(List<TypeRefComponent> type, String baseDefinition, StructureDefinition src) { 1281 StructureDefinition sd = findProfile(baseDefinition, src); 1282 return sd != null && type.size() == 1 && sd.getType().equals(type.get(0).getCode()); 1283 } 1284 1285 1286 private void checkGroupConstraints(StructureDefinition derived) { 1287 List<ElementDefinition> toRemove = new ArrayList<>(); 1288// List<ElementDefinition> processed = new ArrayList<>(); 1289 for (ElementDefinition element : derived.getSnapshot().getElement()) { 1290 if (!toRemove.contains(element) && !element.hasSlicing() && !"0".equals(element.getMax())) { 1291 checkForChildrenInGroup(derived, toRemove, element); 1292 } 1293 } 1294 derived.getSnapshot().getElement().removeAll(toRemove); 1295 } 1296 1297 private void checkForChildrenInGroup(StructureDefinition derived, List<ElementDefinition> toRemove, ElementDefinition element) throws Error { 1298 List<ElementDefinition> children = getChildren(derived, element); 1299 List<ElementChoiceGroup> groups = readChoices(element, children); 1300 for (ElementChoiceGroup group : groups) { 1301 String mandated = null; 1302 Set<String> names = new HashSet<>(); 1303 for (ElementDefinition ed : children) { 1304 String name = tail(ed.getPath()); 1305 if (names.contains(name)) { 1306 throw new Error("huh?"); 1307 } else { 1308 names.add(name); 1309 } 1310 if (group.getElements().contains(name)) { 1311 if (ed.getMin() == 1) { 1312 if (mandated == null) { 1313 mandated = name; 1314 } else { 1315 throw new Error("Error: there are two mandatory elements in "+derived.getUrl()+" when there can only be one: "+mandated+" and "+name); 1316 } 1317 } 1318 } 1319 } 1320 if (mandated != null) { 1321 for (ElementDefinition ed : children) { 1322 String name = tail(ed.getPath()); 1323 if (group.getElements().contains(name) && !mandated.equals(name)) { 1324 ed.setMax("0"); 1325 addAllChildren(derived, ed, toRemove); 1326 } 1327 } 1328 } 1329 } 1330 } 1331 1332 private List<ElementDefinition> getChildren(StructureDefinition derived, ElementDefinition element) { 1333 List<ElementDefinition> elements = derived.getSnapshot().getElement(); 1334 int index = elements.indexOf(element) + 1; 1335 String path = element.getPath()+"."; 1336 List<ElementDefinition> list = new ArrayList<>(); 1337 while (index < elements.size()) { 1338 ElementDefinition e = elements.get(index); 1339 String p = e.getPath(); 1340 if (p.startsWith(path) && !e.hasSliceName()) { 1341 if (!p.substring(path.length()).contains(".")) { 1342 list.add(e); 1343 } 1344 index++; 1345 } else { 1346 break; 1347 } 1348 } 1349 return list; 1350 } 1351 1352 private void addAllChildren(StructureDefinition derived, ElementDefinition element, List<ElementDefinition> toRemove) { 1353 List<ElementDefinition> children = getChildList(derived, element); 1354 for (ElementDefinition child : children) { 1355 toRemove.add(child); 1356 addAllChildren(derived, child, toRemove); 1357 } 1358 } 1359 1360 /** 1361 * Check that a differential is valid. 1362 * @param elements 1363 * @param type 1364 * @param url 1365 */ 1366 private void checkDifferential(List<ElementDefinition> elements, String type, String url) { 1367 boolean first = true; 1368 String t = urlTail(type); 1369 for (ElementDefinition ed : elements) { 1370 if (!ed.hasPath()) { 1371 throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url)); 1372 } 1373 String p = ed.getPath(); 1374 if (p == null) { 1375 throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_VALUE_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url)); 1376 } 1377 if (!((first && t.equals(p)) || p.startsWith(t+"."))) { 1378 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__MUST_START_WITH_, p, url, t, (first ? " (or be '"+t+"')" : ""))); 1379 } 1380 if (p.contains(".")) { 1381 // Element names (the parts of a path delineated by the '.' character) SHALL NOT contain whitespace (i.e. Unicode characters marked as whitespace) 1382 // Element names SHALL NOT contain the characters ,:;'"/|?!@#$%^&*()[]{} 1383 // Element names SHOULD not contain non-ASCII characters 1384 // Element names SHALL NOT exceed 64 characters in length 1385 String[] pl = p.split("\\."); 1386 for (String pp : pl) { 1387 if (pp.length() < 1) { 1388 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_MISING_, p, url)); 1389 } 1390 if (pp.length() > 64) { 1391 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_EXCEEDS_64_CHARS_IN_LENGTH, p, url)); 1392 } 1393 for (char ch : pp.toCharArray()) { 1394 if (Utilities.isWhitespace(ch)) { 1395 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NO_UNICODE_WHITESPACE, p, url)); 1396 } 1397 if (Utilities.existsInList(ch, ',', ':', ';', '\'', '"', '/', '|', '?', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '{', '}')) { 1398 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch)); 1399 } 1400 if (ch < ' ' || ch > 'z') { 1401 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch)); 1402 } 1403 } 1404 if (pp.contains("[") || pp.contains("]")) { 1405 if (!pp.endsWith("[x]") || (pp.substring(0, pp.length()-3).contains("[") || (pp.substring(0, pp.length()-3).contains("]")))) { 1406 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTERS_, p, url)); 1407 } 1408 } 1409 } 1410 } 1411 } 1412 } 1413 1414 1415 private boolean isCompatibleType(String base, StructureDefinition sdt) { 1416 StructureDefinition sdb = context.fetchTypeDefinition(base); 1417 if (sdb.getType().equals(sdt.getType())) { 1418 return true; 1419 } 1420 StructureDefinition sd = context.fetchTypeDefinition(sdt.getType()); 1421 while (sd != null) { 1422 if (sd.getType().equals(sdb.getType())) { 1423 return true; 1424 } 1425 if (sd.getUrl().equals(sdb.getUrl())) { 1426 return true; 1427 } 1428 sd = findProfile(sd.getBaseDefinition(), sd); 1429 } 1430 return false; 1431 } 1432 1433 1434 private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) { 1435 StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent(); 1436 for (ElementDefinition sed : source.getElement()) { 1437 ElementDefinition ted = sed.copy(); 1438 diff.getElement().add(ted); 1439 ted.setUserData(UserDataNames.SNAPSHOT_diff_source, sed); 1440 } 1441 return diff; 1442 } 1443 1444 private StructureDefinitionSnapshotComponent cloneSnapshot(StructureDefinitionSnapshotComponent source, String baseType, String derivedType) { 1445 StructureDefinitionSnapshotComponent diff = new StructureDefinitionSnapshotComponent(); 1446 for (ElementDefinition sed : source.getElement()) { 1447 ElementDefinition ted = sed.copy(); 1448 ted.setId(ted.getId().replaceFirst(baseType,derivedType)); 1449 ted.setPath(ted.getPath().replaceFirst(baseType,derivedType)); 1450 diff.getElement().add(ted); 1451 } 1452 return diff; 1453 } 1454 1455 private String constraintSummary(ElementDefinition ed) { 1456 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1457 if (ed.hasPattern()) 1458 b.append("pattern="+ed.getPattern().fhirType()); 1459 if (ed.hasFixed()) 1460 b.append("fixed="+ed.getFixed().fhirType()); 1461 if (ed.hasConstraint()) 1462 b.append("constraints="+ed.getConstraint().size()); 1463 return b.toString(); 1464 } 1465 1466 1467 private String sliceSummary(ElementDefinition ed) { 1468 if (!ed.hasSlicing() && !ed.hasSliceName()) 1469 return ""; 1470 if (ed.hasSliceName()) 1471 return " (slicename = "+ed.getSliceName()+")"; 1472 1473 StringBuilder b = new StringBuilder(); 1474 boolean first = true; 1475 for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) { 1476 if (first) 1477 first = false; 1478 else 1479 b.append("|"); 1480 b.append(d.getPath()); 1481 } 1482 return " (slicing by "+b.toString()+")"; 1483 } 1484 1485 1486// private String typeSummary(ElementDefinition ed) { 1487// StringBuilder b = new StringBuilder(); 1488// boolean first = true; 1489// for (TypeRefComponent tr : ed.getType()) { 1490// if (first) 1491// first = false; 1492// else 1493// b.append("|"); 1494// b.append(tr.getWorkingCode()); 1495// } 1496// return b.toString(); 1497// } 1498 1499 private String typeSummaryWithProfile(ElementDefinition ed) { 1500 StringBuilder b = new StringBuilder(); 1501 boolean first = true; 1502 for (TypeRefComponent tr : ed.getType()) { 1503 if (first) 1504 first = false; 1505 else 1506 b.append("|"); 1507 b.append(tr.getWorkingCode()); 1508 if (tr.hasProfile()) { 1509 b.append("("); 1510 b.append(tr.getProfile()); 1511 b.append(")"); 1512 1513 } 1514 } 1515 return b.toString(); 1516 } 1517 1518 1519// private boolean findMatchingElement(String id, List<ElementDefinition> list) { 1520// for (ElementDefinition ed : list) { 1521// if (ed.getId().equals(id)) 1522// return true; 1523// if (id.endsWith("[x]")) { 1524// if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains(".")) 1525// return true; 1526// } 1527// } 1528// return false; 1529// } 1530 1531 protected ElementDefinition getById(List<ElementDefinition> list, String baseId) { 1532 for (ElementDefinition t : list) { 1533 if (baseId.equals(t.getId())) { 1534 return t; 1535 } 1536 } 1537 return null; 1538 } 1539 1540 protected void updateConstraintSources(ElementDefinition ed, String url) { 1541 for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { 1542 if (!c.hasSource()) { 1543 c.setSource(url); 1544 } 1545 } 1546 1547 } 1548 1549 protected Set<String> getListOfTypes(ElementDefinition e) { 1550 Set<String> result = new HashSet<>(); 1551 for (TypeRefComponent t : e.getType()) { 1552 result.add(t.getCode()); 1553 } 1554 return result; 1555 } 1556 1557 StructureDefinition getTypeForElement(StructureDefinitionDifferentialComponent differential, int diffCursor, String profileName, 1558 List<ElementDefinition> diffMatches, ElementDefinition outcome, String webUrl, StructureDefinition srcSD) { 1559 if (outcome.getType().size() == 0) { 1560 if (outcome.hasContentReference()) { 1561 throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_CONTENT_REFERENCE_IN_THIS_CONTEXT, outcome.getContentReference(), outcome.getId(), outcome.getPath())); 1562 } else { 1563 throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), profileName)); 1564 } 1565 } 1566 if (outcome.getType().size() > 1) { 1567 for (TypeRefComponent t : outcome.getType()) { 1568 if (!t.getWorkingCode().equals("Reference")) 1569 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)); 1570 } 1571 } 1572 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0), webUrl, srcSD); 1573 if (dt == null) 1574 throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath())); 1575 return dt; 1576 } 1577 1578 protected String sliceNames(List<ElementDefinition> diffMatches) { 1579 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1580 for (ElementDefinition ed : diffMatches) { 1581 if (ed.hasSliceName()) { 1582 b.append(ed.getSliceName()); 1583 } 1584 } 1585 return b.toString(); 1586 } 1587 1588 protected boolean isMatchingType(StructureDefinition sd, List<TypeRefComponent> types, String inner) { 1589 StructureDefinition tsd = sd; 1590 while (tsd != null) { 1591 for (TypeRefComponent tr : types) { 1592 if (tsd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && tsd.getType().equals(tr.getCode())) { 1593 return true; 1594 } 1595 if (inner == null && tsd.getUrl().equals(tr.getCode())) { 1596 return true; 1597 } 1598 if (inner != null) { 1599 ElementDefinition ed = null; 1600 for (ElementDefinition t : tsd.getSnapshot().getElement()) { 1601 if (inner.equals(t.getId())) { 1602 ed = t; 1603 } 1604 } 1605 if (ed != null) { 1606 return isMatchingType(ed.getType(), types); 1607 } 1608 } 1609 } 1610 tsd = findProfile(tsd.getBaseDefinition(), tsd); 1611 } 1612 return false; 1613 } 1614 1615 private boolean isMatchingType(List<TypeRefComponent> test, List<TypeRefComponent> desired) { 1616 for (TypeRefComponent t : test) { 1617 for (TypeRefComponent d : desired) { 1618 if (t.getCode().equals(d.getCode())) { 1619 return true; 1620 } 1621 } 1622 } 1623 return false; 1624 } 1625 1626 protected boolean isValidType(TypeRefComponent t, ElementDefinition base) { 1627 for (TypeRefComponent tr : base.getType()) { 1628 if (tr.getCode().equals(t.getCode())) { 1629 return true; 1630 } 1631 if (tr.getWorkingCode().equals(t.getCode())) { 1632 log.error("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath()); 1633 return true; 1634 } 1635 } 1636 return false; 1637 } 1638 1639 protected void checkNotGenerating(StructureDefinition sd, String role) { 1640 if (sd.isGeneratingSnapshot()) { 1641 throw new FHIRException(context.formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, sd.getUrl(), role)); 1642 } 1643 } 1644 1645 protected boolean isBaseResource(List<TypeRefComponent> types) { 1646 if (types.isEmpty()) 1647 return false; 1648 for (TypeRefComponent type : types) { 1649 String t = type.getWorkingCode(); 1650 if ("Resource".equals(t)) 1651 return false; 1652 } 1653 return true; 1654 1655 } 1656 1657 String determineFixedType(List<ElementDefinition> diffMatches, String fixedType, int i) { 1658 if (diffMatches.get(i).getType().size() == 0 && diffMatches.get(i).hasSliceName()) { 1659 String n = tail(diffMatches.get(i).getPath()).replace("[x]", ""); 1660 String t = diffMatches.get(i).getSliceName().substring(n.length()); 1661 if (isDataType(t)) { 1662 fixedType = t; 1663 } else if (isPrimitive(Utilities.uncapitalize(t))) { 1664 fixedType = Utilities.uncapitalize(t); 1665 } else { 1666 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())); 1667 } 1668 } else if (diffMatches.get(i).getType().size() == 1) { 1669 fixedType = diffMatches.get(i).getType().get(0).getCode(); 1670 } else { 1671 throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__1_AT_, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName())); 1672 } 1673 return fixedType; 1674 } 1675 1676 1677 protected BaseTypeSlice chooseMatchingBaseSlice(List<BaseTypeSlice> baseSlices, String type) { 1678 for (BaseTypeSlice bs : baseSlices) { 1679 if (bs.getType().equals(type)) { 1680 return bs; 1681 } 1682 } 1683 return null; 1684 } 1685 1686 1687 protected List<BaseTypeSlice> findBaseSlices(StructureDefinitionSnapshotComponent list, int start) { 1688 List<BaseTypeSlice> res = new ArrayList<>(); 1689 ElementDefinition base = list.getElement().get(start); 1690 int i = start + 1; 1691 while (i < list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) { 1692 i++; 1693 }; 1694 while (i < list.getElement().size() && list.getElement().get(i).getPath().equals(base.getPath()) && list.getElement().get(i).hasSliceName()) { 1695 int s = i; 1696 i++; 1697 while (i < list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) { 1698 i++; 1699 }; 1700 res.add(new BaseTypeSlice(list.getElement().get(s), list.getElement().get(s).getTypeFirstRep().getCode(), s, i-1)); 1701 } 1702 return res; 1703 } 1704 1705 1706 protected String getWebUrl(StructureDefinition dt, String webUrl) { 1707 if (dt.hasWebPath()) { 1708 // this is a hack, but it works for now, since we don't have deep folders 1709 String url = dt.getWebPath(); 1710 int i = url.lastIndexOf("/"); 1711 if (i < 1) { 1712 return defWebRoot; 1713 } else { 1714 return url.substring(0, i+1); 1715 } 1716 } else { 1717 return webUrl; 1718 } 1719 } 1720 1721 protected String descED(List<ElementDefinition> list, int index) { 1722 return index >=0 && index < list.size() ? list.get(index).present() : "X"; 1723 } 1724 1725 1726 1727 protected String rootName(String cpath) { 1728 String t = tail(cpath); 1729 return t.replace("[x]", ""); 1730 } 1731 1732 1733 protected String determineTypeSlicePath(String path, String cpath) { 1734 String headP = path.substring(0, path.lastIndexOf(".")); 1735// String tailP = path.substring(path.lastIndexOf(".")+1); 1736 String tailC = cpath.substring(cpath.lastIndexOf(".")+1); 1737 return headP+"."+tailC; 1738 } 1739 1740 1741 protected boolean isImplicitSlicing(ElementDefinition ed, String path) { 1742 if (ed == null || ed.getPath() == null || path == null) 1743 return false; 1744 if (path.equals(ed.getPath())) 1745 return false; 1746 boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3)); 1747 return ok; 1748 } 1749 1750 1751 protected boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) { 1752// if (diffMatches.size() < 2) 1753 // return false; 1754 String p = diffMatches.get(0).getPath(); 1755 if (!p.endsWith("[x]") && !cPath.endsWith("[x]")) 1756 return false; 1757 typeList.clear(); 1758 String rn = tail(cPath); 1759 rn = rn.substring(0, rn.length()-3); 1760 for (int i = 0; i < diffMatches.size(); i++) { 1761 ElementDefinition ed = diffMatches.get(i); 1762 String n = tail(ed.getPath()); 1763 if (!n.startsWith(rn)) 1764 return false; 1765 String s = n.substring(rn.length()); 1766 if (!s.contains(".")) { 1767 if (ed.hasSliceName() && ed.getType().size() == 1) { 1768 typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode())); 1769 } else if (ed.hasSliceName() && ed.getType().size() == 0) { 1770 if (isDataType(s)) { 1771 typeList.add(new TypeSlice(ed, s)); 1772 } else if (isPrimitive(Utilities.uncapitalize(s))) { 1773 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 1774 } else { 1775 String tn = ed.getSliceName().substring(n.length()); 1776 if (isDataType(tn)) { 1777 typeList.add(new TypeSlice(ed, tn)); 1778 } else if (isPrimitive(Utilities.uncapitalize(tn))) { 1779 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(tn))); 1780 } 1781 } 1782 } else if (!ed.hasSliceName() && !s.equals("[x]")) { 1783 if (isDataType(s)) 1784 typeList.add(new TypeSlice(ed, s)); 1785 else if (isConstrainedDataType(s)) 1786 typeList.add(new TypeSlice(ed, baseType(s))); 1787 else if (isPrimitive(Utilities.uncapitalize(s))) 1788 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 1789 } else if (!ed.hasSliceName() && s.equals("[x]")) 1790 typeList.add(new TypeSlice(ed, null)); 1791 } 1792 } 1793 return true; 1794 } 1795 1796 1797 protected List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, String path) { 1798 List<ElementRedirection> result = new ArrayList<ElementRedirection>(); 1799 result.addAll(redirector); 1800 result.add(new ElementRedirection(outcome, path)); 1801 return result; 1802 } 1803 1804 1805 protected List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) { 1806 List<TypeRefComponent> res = new ArrayList<TypeRefComponent>(); 1807 for (TypeRefComponent tr : type) { 1808 if (t.equals(tr.getWorkingCode())) 1809 res.add(tr); 1810 } 1811 return res; 1812 } 1813 1814 1815 protected void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) { 1816 outcome.setContentReference(null); 1817 outcome.getType().clear(); // though it should be clear anyway 1818 outcome.getType().addAll(tgt.getType()); 1819 } 1820 1821 1822 protected boolean baseWalksInto(List<ElementDefinition> elements, int cursor) { 1823 if (cursor >= elements.size()) 1824 return false; 1825 String path = elements.get(cursor).getPath(); 1826 String prevPath = elements.get(cursor - 1).getPath(); 1827 return path.startsWith(prevPath + "."); 1828 } 1829 1830 1831 protected ElementDefinition fillOutFromBase(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError { 1832 ElementDefinition res = profile.copy(); 1833 if (!res.hasSliceName()) 1834 res.setSliceName(usage.getSliceName()); 1835 if (!res.hasLabel()) 1836 res.setLabel(usage.getLabel()); 1837 for (Coding c : usage.getCode()) 1838 if (!res.hasCode(c)) 1839 res.addCode(c); 1840 1841 if (!res.hasDefinition()) 1842 res.setDefinition(usage.getDefinition()); 1843 if (!res.hasShort() && usage.hasShort()) 1844 res.setShort(usage.getShort()); 1845 if (!res.hasComment() && usage.hasComment()) 1846 res.setComment(usage.getComment()); 1847 if (!res.hasRequirements() && usage.hasRequirements()) 1848 res.setRequirements(usage.getRequirements()); 1849 for (StringType c : usage.getAlias()) 1850 if (!res.hasAlias(c.getValue())) 1851 res.addAlias(c.getValue()); 1852 if (!res.hasMin() && usage.hasMin()) 1853 res.setMin(usage.getMin()); 1854 if (!res.hasMax() && usage.hasMax()) 1855 res.setMax(usage.getMax()); 1856 1857 if (!res.hasFixed() && usage.hasFixed()) 1858 res.setFixed(usage.getFixed()); 1859 if (!res.hasPattern() && usage.hasPattern()) 1860 res.setPattern(usage.getPattern()); 1861 if (!res.hasExample() && usage.hasExample()) 1862 res.setExample(usage.getExample()); 1863 if (!res.hasMinValue() && usage.hasMinValue()) 1864 res.setMinValue(usage.getMinValue()); 1865 if (!res.hasMaxValue() && usage.hasMaxValue()) 1866 res.setMaxValue(usage.getMaxValue()); 1867 if (!res.hasMaxLength() && usage.hasMaxLength()) 1868 res.setMaxLength(usage.getMaxLength()); 1869 if (!res.hasMustSupport() && usage.hasMustSupport()) 1870 res.setMustSupport(usage.getMustSupport()); 1871 if (!res.hasIsSummary() && usage.hasIsSummary()) 1872 res.setIsSummary(usage.getIsSummary()); 1873 if (!res.hasIsModifier() && usage.hasIsModifier()) 1874 res.setIsModifier(usage.getIsModifier()); 1875 if (!res.hasIsModifierReason() && usage.hasIsModifierReason()) 1876 res.setIsModifierReason(usage.getIsModifierReason()); 1877 if (!res.hasMustHaveValue() && usage.hasMustHaveValue()) 1878 res.setMustHaveValue(usage.getMustHaveValue()); 1879 if (!res.hasBinding() && usage.hasBinding()) 1880 res.setBinding(usage.getBinding().copy()); 1881 for (ElementDefinitionConstraintComponent c : usage.getConstraint()) 1882 if (!res.hasConstraint(c.getKey())) 1883 res.addConstraint(c); 1884 for (Extension e : usage.getExtension()) { 1885 if (!res.hasExtension(e.getUrl())) 1886 res.addExtension(e.copy()); 1887 } 1888 1889 return res; 1890 } 1891 1892 1893 protected boolean checkExtensionDoco(ElementDefinition base) { 1894 // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff 1895 boolean isExtension = (base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension")) && 1896 (!base.hasBase() || !"II.extension".equals(base.getBase().getPath())); 1897 if (isExtension) { 1898 base.setDefinition("An Extension"); 1899 base.setShort("Extension"); 1900 base.setCommentElement(null); 1901 base.setRequirementsElement(null); 1902 base.getAlias().clear(); 1903 base.getMapping().clear(); 1904 } 1905 return isExtension; 1906 } 1907 1908 1909 protected String pathTail(List<ElementDefinition> diffMatches, int i) { 1910 1911 ElementDefinition d = diffMatches.get(i); 1912 String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath(); 1913 return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : ""); 1914 } 1915 1916 1917 protected void markDerived(ElementDefinition outcome) { 1918 for (ElementDefinitionConstraintComponent inv : outcome.getConstraint()) 1919 inv.setUserData(UserDataNames.SNAPSHOT_IS_DERIVED, true); 1920 } 1921 1922 1923 static String summarizeSlicing(ElementDefinitionSlicingComponent slice) { 1924 StringBuilder b = new StringBuilder(); 1925 boolean first = true; 1926 for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) { 1927 if (first) 1928 first = false; 1929 else 1930 b.append(", "); 1931 b.append(d.getType().toCode()+":"+d.getPath()); 1932 } 1933 b.append(" ("); 1934 if (slice.hasOrdered()) 1935 b.append(slice.getOrdered() ? "ordered" : "unordered"); 1936 b.append("/"); 1937 if (slice.hasRules()) 1938 b.append(slice.getRules().toCode()); 1939 b.append(")"); 1940 if (slice.hasDescription()) { 1941 b.append(" \""); 1942 b.append(slice.getDescription()); 1943 b.append("\""); 1944 } 1945 return b.toString(); 1946 } 1947 1948 1949 protected void updateFromBase(ElementDefinition derived, ElementDefinition base, String baseProfileUrl) { 1950 derived.setUserData(UserDataNames.SNAPSHOT_BASE_MODEL, baseProfileUrl); 1951 derived.setUserData(UserDataNames.SNAPSHOT_BASE_PATH, base.getPath()); 1952 if (base.hasBase()) { 1953 if (!derived.hasBase()) 1954 derived.setBase(new ElementDefinitionBaseComponent()); 1955 derived.getBase().setPath(base.getBase().getPath()); 1956 derived.getBase().setMin(base.getBase().getMin()); 1957 derived.getBase().setMax(base.getBase().getMax()); 1958 } else { 1959 if (!derived.hasBase()) 1960 derived.setBase(new ElementDefinitionBaseComponent()); 1961 derived.getBase().setPath(base.getPath()); 1962 derived.getBase().setMin(base.getMin()); 1963 derived.getBase().setMax(base.getMax()); 1964 } 1965 } 1966 1967 1968 protected boolean pathStartsWith(String p1, String p2) { 1969 return p1.startsWith(p2) || (p2.endsWith("[x].") && p1.startsWith(p2.substring(0, p2.length()-4))); 1970 } 1971 1972 private boolean pathMatches(String p1, String p2) { 1973 return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains(".")); 1974 } 1975 1976 1977 protected String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) { 1978 if (contextPath == null) 1979 return pathSimple; 1980// String ptail = pathSimple.substring(contextPath.length() + 1); 1981 if (redirector != null && redirector.size() > 0) { 1982 String ptail = null; 1983 if (contextPath.length() >= pathSimple.length()) { 1984 ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1985 } else { 1986 ptail = pathSimple.substring(contextPath.length()+1); 1987 } 1988 return redirector.get(redirector.size()-1).getPath()+"."+ptail; 1989// return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1); 1990 } else { 1991 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1992 return contextPath+"."+ptail; 1993 } 1994 } 1995 1996 protected String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, String redirectSource) { 1997 String s; 1998 if (contextPath == null) 1999 s = pathSimple; 2000 else { 2001 if (redirector != null && redirector.size() > 0) { 2002 String ptail = null; 2003 if (redirectSource.length() >= pathSimple.length()) { 2004 ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 2005 } else { 2006 ptail = pathSimple.substring(redirectSource.length()+1); 2007 } 2008 // ptail = ptail.substring(ptail.indexOf(".")+1); 2009 s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail; 2010 } else { 2011 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 2012 s = contextPath+"."+ptail; 2013 } 2014 } 2015 return s; 2016 } 2017 2018 protected StructureDefinition getProfileForDataType(TypeRefComponent type, String webUrl, StructureDefinition src) { 2019 StructureDefinition sd = null; 2020 if (type.hasProfile()) { 2021 sd = findProfile(type.getProfile().get(0).getValue(), src); 2022 if (sd == null) { 2023 if (makeXVer().matchingUrl(type.getProfile().get(0).getValue()) && xver.status(type.getProfile().get(0).getValue()) == XVerExtensionStatus.Valid) { 2024 sd = xver.getDefinition(type.getProfile().get(0).getValue()); 2025 generateSnapshot(context.fetchTypeDefinition("Extension"), sd, sd.getUrl(), webUrl, sd.getName()); 2026 } 2027 } 2028 if (sd == null) { 2029 log.debug("Failed to find referenced profile: " + type.getProfile()); 2030 } 2031 2032 } 2033 if (sd == null) 2034 sd = context.fetchTypeDefinition(type.getWorkingCode()); 2035 if (sd == null) 2036 log.warn("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM 2037 return sd; 2038 } 2039 2040 protected StructureDefinition getProfileForDataType(String type) { 2041 StructureDefinition sd = context.fetchTypeDefinition(type); 2042 if (sd == null) 2043 log.warn("XX: failed to find profle for type: " + type); // debug GJM 2044 return sd; 2045 } 2046 2047 static String typeCode(List<TypeRefComponent> types) { 2048 StringBuilder b = new StringBuilder(); 2049 boolean first = true; 2050 for (TypeRefComponent type : types) { 2051 if (first) first = false; else b.append(", "); 2052 b.append(type.getWorkingCode()); 2053 if (type.hasTargetProfile()) 2054 b.append("{"+type.getTargetProfile()+"}"); 2055 else if (type.hasProfile()) 2056 b.append("{"+type.getProfile()+"}"); 2057 } 2058 return b.toString(); 2059 } 2060 2061 2062 protected boolean isDataType(List<TypeRefComponent> types) { 2063 if (types.isEmpty()) 2064 return false; 2065 for (TypeRefComponent type : types) { 2066 String t = type.getWorkingCode(); 2067 if (!isDataType(t) && !isPrimitive(t)) 2068 return false; 2069 } 2070 return true; 2071 } 2072 2073 2074 /** 2075 * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url 2076 * @param url - the base url to use to turn internal references into absolute references 2077 * @param element - the Element to update 2078 * @return - the updated Element 2079 */ 2080 public ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element, boolean processRelatives) { 2081 if (element != null) { 2082 ElementDefinition defn = element; 2083 if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#")) 2084 defn.getBinding().setValueSet(url+defn.getBinding().getValueSet()); 2085 for (TypeRefComponent t : defn.getType()) { 2086 for (UriType u : t.getProfile()) { 2087 if (u.getValue().startsWith("#")) 2088 u.setValue(url+t.getProfile()); 2089 } 2090 for (UriType u : t.getTargetProfile()) { 2091 if (u.getValue().startsWith("#")) 2092 u.setValue(url+t.getTargetProfile()); 2093 } 2094 } 2095 if (webUrl != null) { 2096 // also, must touch up the markdown 2097 if (element.hasDefinition()) { 2098 element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2099 } 2100 if (element.hasComment()) { 2101 element.setComment(processRelativeUrls(element.getComment(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2102 } 2103 if (element.hasRequirements()) { 2104 element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2105 } 2106 if (element.hasMeaningWhenMissing()) { 2107 element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2108 } 2109 if (element.hasBinding() && element.getBinding().hasDescription()) { 2110 element.getBinding().setDescription(processRelativeUrls(element.getBinding().getDescription(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2111 } 2112 for (Extension ext : element.getExtension()) { 2113 if (ext.hasValueMarkdownType()) { 2114 MarkdownType md = ext.getValueMarkdownType(); 2115 md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives)); 2116 } 2117 } 2118 } 2119 } 2120 return element; 2121 } 2122 2123 2124 public static String processRelativeUrls(String markdown, String webUrl, String basePath, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames, boolean processRelatives) { 2125 if (markdown == null) { 2126 return ""; 2127 } 2128 Set<String> anchorRefs = new HashSet<>(); 2129 markdown = markdown+" "; 2130 2131 StringBuilder b = new StringBuilder(); 2132 int i = 0; 2133 int left = -1; 2134 boolean processingLink = false; 2135 int linkLeft = -1; 2136 while (i < markdown.length()) { 2137 if (markdown.charAt(i) == '[') { 2138 if (left == -1) { 2139 left = i; 2140 } else { 2141 left = Integer.MAX_VALUE; 2142 } 2143 } 2144 if (markdown.charAt(i) == ']') { 2145 if (left != -1 && left != Integer.MAX_VALUE && markdown.length() > i && markdown.charAt(i+1) != '(') { 2146 String n = markdown.substring(left+1, i); 2147 if (anchorRefs.contains(n) && markdown.length() > i && markdown.charAt(i+1) == ':') { 2148 processingLink = true; 2149 } else { 2150 anchorRefs.add(n); 2151 } 2152 } 2153 left = -1; 2154 } 2155 if (processingLink) { 2156 char ch = markdown.charAt(i); 2157 if (linkLeft == -1) { 2158 if (ch != ']' && ch != ':' && !Character.isWhitespace(ch)) { 2159 linkLeft = i; 2160 } else { 2161 b.append(ch); 2162 } 2163 } else { 2164 if (Character.isWhitespace(ch)) { 2165 // found the end of the processible link: 2166 String url = markdown.substring(linkLeft, i); 2167 if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames, webUrl)) { 2168 b.append(basePath); 2169 if (!Utilities.noString(basePath) && !basePath.endsWith("/")) { 2170 b.append("/"); 2171 } 2172 } 2173 b.append(url); 2174 b.append(ch); 2175 linkLeft = -1; 2176 } 2177 } 2178 } else { 2179 if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { 2180 2181 int j = i + 2; 2182 while (j < markdown.length() && markdown.charAt(j) != ')') 2183 j++; 2184 if (j < markdown.length()) { 2185 String url = markdown.substring(i+2, j); 2186 if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) { 2187 // 2188 // In principle, relative URLs are supposed to be converted to absolute URLs in snapshots. 2189 // that's what this code is doing. 2190 // 2191 // But that hasn't always happened and there's packages out there where the snapshots 2192 // contain relative references that actually are references to the main specification 2193 // 2194 // This code is trying to guess which relative references are actually to the 2195 // base specification. 2196 // 2197 if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames, webUrl)) { 2198 b.append("]("); 2199 b.append(basePath); 2200 if (!Utilities.noString(basePath) && !basePath.endsWith("/")) { 2201 b.append("/"); 2202 } 2203 i = i + 1; 2204 } else { 2205 b.append("]("); 2206 // disabled 7-Dec 2021 GDG - we don't want to fool with relative URLs at all? 2207 // 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 2208 // added processRelatives parameter to deal with this (well, to try) 2209 if (processRelatives && webUrl != null && !issLocalFileName(url, localFilenames)) { 2210 2211 b.append(webUrl); 2212 if (!Utilities.noString(webUrl) && !webUrl.endsWith("/")) { 2213 b.append("/"); 2214 } 2215 } else { 2216 //DO NOTHING 2217 } 2218 i = i + 1; 2219 } 2220 } else 2221 b.append(markdown.charAt(i)); 2222 } else 2223 b.append(markdown.charAt(i)); 2224 } else { 2225 b.append(markdown.charAt(i)); 2226 } 2227 } 2228 i++; 2229 } 2230 String s = b.toString(); 2231 return Utilities.rightTrim(s); 2232 } 2233 2234 private static boolean issLocalFileName(String url, Set<String> localFilenames) { 2235 if (localFilenames != null) { 2236 for (String n : localFilenames) { 2237 if (url.startsWith(n.toLowerCase())) { 2238 return true; 2239 } 2240 } 2241 } 2242 return false; 2243 } 2244 2245 2246 private static boolean isLikelySourceURLReference(String url, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames, String baseUrl) { 2247 if (url == null) { 2248 return false; 2249 } 2250 if (baseUrl != null && !baseUrl.startsWith("http://hl7.org/fhir/R")) { 2251 if (resourceNames != null) { 2252 for (String n : resourceNames) { 2253 if (n != null && url.startsWith(n.toLowerCase()+".html")) { 2254 return true; 2255 } 2256 if (n != null && url.startsWith(n.toLowerCase()+"-definitions.html")) { 2257 return true; 2258 } 2259 } 2260 } 2261 if (localFilenames != null) { 2262 for (String n : localFilenames) { 2263 if (n != null && url.startsWith(n.toLowerCase())) { 2264 return false; 2265 } 2266 } 2267 } 2268 if (baseFilenames != null) { 2269 for (String n : baseFilenames) { 2270 if (n != null && url.startsWith(n.toLowerCase())) { 2271 return true; 2272 } 2273 } 2274 } 2275 } 2276 return 2277 url.startsWith("extensibility.html") || 2278 url.startsWith("terminologies.html") || 2279 url.startsWith("observation.html") || 2280 url.startsWith("codesystem.html") || 2281 url.startsWith("fhirpath.html") || 2282 url.startsWith("datatypes.html") || 2283 url.startsWith("operations.html") || 2284 url.startsWith("resource.html") || 2285 url.startsWith("elementdefinition.html") || 2286 url.startsWith("element-definitions.html") || 2287 url.startsWith("snomedct.html") || 2288 url.startsWith("loinc.html") || 2289 url.startsWith("http.html") || 2290 url.startsWith("references") || 2291 url.startsWith("license.html") || 2292 url.startsWith("narrative.html") || 2293 url.startsWith("search.html") || 2294 url.startsWith("security.html") || 2295 url.startsWith("versions.html") || 2296 url.startsWith("patient-operation-match.html") || 2297 (url.startsWith("extension-") && url.contains(".html")) || 2298 url.startsWith("resource-definitions.html"); 2299 } 2300 2301 protected List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) { 2302 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 2303 String path = current.getPath(); 2304 int cursor = list.indexOf(current)+1; 2305 while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) { 2306 if (pathMatches(list.get(cursor).getPath(), path)) 2307 result.add(list.get(cursor)); 2308 cursor++; 2309 } 2310 return result; 2311 } 2312 2313 protected void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) { 2314 if (src.hasOrderedElement()) 2315 dst.setOrderedElement(src.getOrderedElement().copy()); 2316 if (src.hasDiscriminator()) { 2317 // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll because it uses object equality, not string equality 2318 for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) { 2319 boolean found = false; 2320 for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) { 2321 if (matches(d, s)) { 2322 found = true; 2323 break; 2324 } 2325 } 2326 if (!found) 2327 dst.getDiscriminator().add(s); 2328 } 2329 } 2330 if (src.hasRulesElement()) 2331 dst.setRulesElement(src.getRulesElement().copy()); 2332 } 2333 2334 protected boolean orderMatches(BooleanType diff, BooleanType base) { 2335 return (diff == null) || (base == null) || (diff.getValue() == base.getValue()); 2336 } 2337 2338 protected boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) { 2339 if (diff.isEmpty() || base.isEmpty()) 2340 return true; 2341 if (diff.size() < base.size()) 2342 return false; 2343 for (int i = 0; i < base.size(); i++) 2344 if (!matches(diff.get(i), base.get(i))) 2345 return false; 2346 return true; 2347 } 2348 2349 private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) { 2350 return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath()); 2351 } 2352 2353 2354 protected boolean ruleMatches(SlicingRules diff, SlicingRules base) { 2355 return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) || 2356 ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED)); 2357 } 2358 2359 protected boolean isSlicedToOneOnly(ElementDefinition e) { 2360 return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); 2361 } 2362 2363 protected boolean isTypeSlicing(ElementDefinition e) { 2364 return (e.hasSlicing() && e.getSlicing().getDiscriminator().size() == 1 && 2365 e.getSlicing().getDiscriminatorFirstRep().getType() == DiscriminatorType.TYPE && 2366 "$this".equals(e.getSlicing().getDiscriminatorFirstRep().getPath())); 2367 } 2368 2369 2370 protected ElementDefinitionSlicingComponent makeExtensionSlicing() { 2371 ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); 2372 slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); 2373 slice.setOrdered(false); 2374 slice.setRules(SlicingRules.OPEN); 2375 return slice; 2376 } 2377 2378 protected boolean isExtension(ElementDefinition currentBase) { 2379 return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension"); 2380 } 2381 2382 boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException { 2383 end = Math.min(context.getElement().size(), end); 2384 start = Math.max(0, start); 2385 2386 for (int i = start; i <= end; i++) { 2387 ElementDefinition ed = context.getElement().get(i); 2388 String statedPath = ed.getPath(); 2389 if (!allowSlices && statedPath.equals(path) && ed.hasSliceName()) { 2390 return false; 2391 } else if (statedPath.startsWith(path+".")) { 2392 return true; 2393 } else if (path.endsWith("[x]") && statedPath.startsWith(path.substring(0, path.length() -3))) { 2394 return true; 2395 } else if (i != start && !allowSlices && !statedPath.startsWith(path+".")) { 2396 return false; 2397 } else if (i != start && allowSlices && !statedPath.startsWith(path)) { 2398 return false; 2399 } else { 2400 // not sure why we get here, but returning false at this point makes a bunch of tests fail 2401 } 2402 } 2403 return false; 2404 } 2405 2406 protected List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException { 2407 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 2408 String[] p = path.split("\\."); 2409 for (int i = start; i <= end; i++) { 2410 String statedPath = context.getElement().get(i).getPath(); 2411 String[] sp = statedPath.split("\\."); 2412 boolean ok = sp.length == p.length; 2413 for (int j = 0; j < p.length; j++) { 2414 ok = ok && sp.length > j && (p[j].equals(sp[j]) || isSameBase(p[j], sp[j])); 2415 } 2416// don't need this debug check - everything is ok 2417// if (ok != (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 && 2418// statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) && 2419// (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains("."))))) { 2420// 2421// } 2422 if (ok) { 2423 /* 2424 * Commenting this out because it raises warnings when profiling inherited elements. For example, 2425 * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry') 2426 * Not sure we have enough information here to do the check properly. Might be better done when we're sorting the profile? 2427 2428 if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath())) 2429 addMessage(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)); 2430 2431 */ 2432 result.add(context.getElement().get(i)); 2433 } 2434 } 2435 if (debug) { 2436 Set<String> ids = new HashSet<>(); 2437 for (ElementDefinition ed : result) { 2438 ids.add(ed.getIdOrPath()); 2439 } 2440 } 2441 return result; 2442 } 2443 2444 2445 private boolean isSameBase(String p, String sp) { 2446 return (p.endsWith("[x]") && sp.startsWith(p.substring(0, p.length()-3))) || (sp.endsWith("[x]") && p.startsWith(sp.substring(0, sp.length()-3))) ; 2447 } 2448 2449 protected int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) { 2450 int result = cursor; 2451 if (cursor >= context.getElement().size()) 2452 return result; 2453 String path = context.getElement().get(cursor).getPath()+"."; 2454 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 2455 result++; 2456 return result; 2457 } 2458 2459 protected int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) { 2460 int result = cursor; 2461 String path = context.getElement().get(cursor).getPath()+"."; 2462 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 2463 result++; 2464 return result; 2465 } 2466 2467 protected int findEndOfElementNoSlices(StructureDefinitionSnapshotComponent context, int cursor) { 2468 int result = cursor; 2469 String path = context.getElement().get(cursor).getPath()+"."; 2470 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path) && !context.getElement().get(result+1).hasSliceName()) 2471 result++; 2472 return result; 2473 } 2474 2475 protected boolean unbounded(ElementDefinition definition) { 2476 StringType max = definition.getMaxElement(); 2477 if (max == null) 2478 return false; // this is not valid 2479 if (max.getValue().equals("1")) 2480 return false; 2481 if (max.getValue().equals("0")) 2482 return false; 2483 return true; 2484 } 2485 2486 2487 public void updateFromObligationProfiles(ElementDefinition base) { 2488 List<ElementDefinition> obligationProfileElements = new ArrayList<>(); 2489 for (StructureDefinition sd : obligationProfiles) { 2490 ElementDefinition ed = sd.getSnapshot().getElementById(base.getId()); 2491 if (ed != null) { 2492 obligationProfileElements.add(ed); 2493 } 2494 } 2495 for (ElementDefinition ed : obligationProfileElements) { 2496 for (Extension ext : ed.getExtension()) { 2497 if (Utilities.existsInList(ext.getUrl(), ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) { 2498 base.getExtension().add(ext.copy()); 2499 } 2500 } 2501 } 2502 boolean hasMustSupport = false; 2503 for (ElementDefinition ed : obligationProfileElements) { 2504 hasMustSupport = hasMustSupport || ed.hasMustSupportElement(); 2505 } 2506 if (hasMustSupport) { 2507 for (ElementDefinition ed : obligationProfileElements) { 2508 mergeExtensions(base.getMustSupportElement(), ed.getMustSupportElement()); 2509 if (ed.getMustSupport()) { 2510 base.setMustSupport(true); 2511 } 2512 } 2513 } 2514 boolean hasBinding = false; 2515 for (ElementDefinition ed : obligationProfileElements) { 2516 hasBinding = hasBinding || ed.hasBinding(); 2517 } 2518 if (hasBinding) { 2519 ElementDefinitionBindingComponent binding = base.getBinding(); 2520 for (ElementDefinition ed : obligationProfileElements) { 2521 for (Extension ext : ed.getBinding().getExtension()) { 2522 if (ExtensionDefinitions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) { 2523 String p = ext.getExtensionString("purpose"); 2524 if (!Utilities.existsInList(p, "maximum", "required", "extensible")) { 2525 if (!binding.hasExtension(ext)) { 2526 binding.getExtension().add(ext.copy()); 2527 } 2528 } 2529 } 2530 } 2531 for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { 2532 if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) { 2533 if (!binding.hasAdditional(ab)) { 2534 binding.getAdditional().add(ab.copy()); 2535 } 2536 } 2537 } 2538 } 2539 } 2540 } 2541 2542 2543 protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc, String path, MappingAssistant mappings, boolean fromSlicer) throws DefinitionException, FHIRException { 2544 source.setUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT, dest); 2545 // we start with a clone of the base profile ('dest') and we copy from the profile ('source') 2546 // over the top for anything the source has 2547 ElementDefinition base = dest; 2548 ElementDefinition derived = source; 2549 derived.setUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER, base); 2550 boolean isExtension = checkExtensionDoco(base); 2551 List<ElementDefinition> obligationProfileElements = new ArrayList<>(); 2552 for (StructureDefinition sd : obligationProfiles) { 2553 ElementDefinition ed = sd.getSnapshot().getElementById(base.getId()); 2554 if (ed != null) { 2555 obligationProfileElements.add(ed); 2556 } 2557 } 2558 2559 // hack workaround for problem in R5 snapshots 2560 List<Extension> elist = dest.getExtensionsByUrl(ExtensionDefinitions.EXT_TRANSLATABLE); 2561 if (elist.size() == 2) { 2562 dest.getExtension().remove(elist.get(1)); 2563 } 2564 updateExtensionsFromDefinition(dest, source, derivedSrc, srcSD); 2565 2566 for (ElementDefinition ed : obligationProfileElements) { 2567 for (Extension ext : ed.getExtension()) { 2568 if (Utilities.existsInList(ext.getUrl(), ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) { 2569 dest.getExtension().add(new Extension(ExtensionDefinitions.EXT_OBLIGATION_CORE, ext.getValue().copy())); 2570 } 2571 } 2572 } 2573 2574 // Before applying changes, apply them to what's in the profile 2575 // but only if it's an extension or a resource 2576 2577 StructureDefinition profile = null; 2578 boolean msg = true; 2579 if (base.hasSliceName()) { 2580 profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? findProfile(base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null; 2581 } 2582 if (profile == null && source.getTypeFirstRep().hasProfile()) { 2583 String pu = source.getTypeFirstRep().getProfile().get(0).getValue(); 2584 profile = findProfile(pu, derivedSrc); 2585 if (profile == null) { 2586 if (makeXVer().matchingUrl(pu)) { 2587 switch (xver.status(pu)) { 2588 case BadVersion: 2589 throw new FHIRException("Reference to invalid version in extension url " + pu); 2590 case Invalid: 2591 throw new FHIRException("Reference to invalid extension " + pu); 2592 case Unknown: 2593 throw new FHIRException("Reference to unknown extension " + pu); 2594 case Valid: 2595 profile = xver.getDefinition(pu); 2596 generateSnapshot(context.fetchTypeDefinition("Extension"), profile, profile.getUrl(), context.getSpecUrl(), profile.getName()); 2597 } 2598 } 2599 2600 } 2601 if (profile != null && !"Extension".equals(profile.getType()) && profile.getKind() != StructureDefinitionKind.RESOURCE && profile.getKind() != StructureDefinitionKind.LOGICAL) { 2602 // this is a problem - we're kind of hacking things here. The problem is that we sometimes want the details from the profile to override the 2603 // inherited attributes, and sometimes not 2604 profile = null; 2605 msg = false; 2606 } 2607 } 2608 if (profile != null && (profile.getKind() == StructureDefinitionKind.RESOURCE || "Extension".equals(profile.getType()))) { 2609 if (profile.getSnapshot().getElement().isEmpty()) { 2610 throw new DefinitionException(context.formatMessage(I18nConstants.SNAPSHOT_IS_EMPTY, profile.getVersionedUrl())); 2611 } 2612 ElementDefinition e = profile.getSnapshot().getElement().get(0); 2613 String webroot = profile.getUserString(UserDataNames.render_webroot); 2614 2615 if (e.hasDefinition()) { 2616 base.setDefinition(processRelativeUrls(e.getDefinition(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true)); 2617 } 2618 if (e.getBinding().hasDescription()) { 2619 base.getBinding().setDescription(processRelativeUrls(e.getBinding().getDescription(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true)); 2620 } 2621 base.setShort(e.getShort()); 2622 if (e.hasCommentElement()) 2623 base.setCommentElement(e.getCommentElement()); 2624 if (e.hasRequirementsElement()) 2625 base.setRequirementsElement(e.getRequirementsElement()); 2626 base.getAlias().clear(); 2627 base.getAlias().addAll(e.getAlias()); 2628 base.getMapping().clear(); 2629 base.getMapping().addAll(e.getMapping()); 2630 } else if (source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() && !source.getTypeFirstRep().getProfile().get(0).hasExtension(ExtensionDefinitions.EXT_PROFILE_ELEMENT)) { 2631 // todo: should we change down the profile_element if there's one? 2632 String type = source.getTypeFirstRep().getWorkingCode(); 2633 if (msg) { 2634 if ("Extension".equals(type)) { 2635 log.warn("Can't find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on"); 2636 if (allowUnknownProfile != AllowUnknownProfile.ALL_TYPES) { 2637 throw new DefinitionException("Unable to find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue()); 2638 } 2639 } else { 2640 log.warn("Can't find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on"); 2641 if (allowUnknownProfile == AllowUnknownProfile.NONE) { 2642 throw new DefinitionException("Unable to find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue()); 2643 } 2644 } 2645 } 2646 } 2647 if (derived != null) { 2648 if (derived.hasSliceName()) { 2649 base.setSliceName(derived.getSliceName()); 2650 } 2651 2652 if (derived.hasShortElement()) { 2653 if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false)) 2654 base.setShortElement(derived.getShortElement().copy()); 2655 else if (trimDifferential) 2656 derived.setShortElement(null); 2657 else if (derived.hasShortElement()) 2658 derived.getShortElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2659 } 2660 2661 if (derived.hasDefinitionElement()) { 2662 if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) { 2663 base.setDefinitionElement(mergeMarkdown(derived.getDefinitionElement(), base.getDefinitionElement())); 2664 } else if (trimDifferential) 2665 derived.setDefinitionElement(null); 2666 else if (derived.hasDefinitionElement()) 2667 derived.getDefinitionElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2668 } 2669 2670 if (derived.hasCommentElement()) { 2671 if (!Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false)) 2672 base.setCommentElement(mergeMarkdown(derived.getCommentElement(), base.getCommentElement())); 2673 else if (trimDifferential) 2674 base.setCommentElement(derived.getCommentElement().copy()); 2675 else if (derived.hasCommentElement()) 2676 derived.getCommentElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2677 } 2678 2679 if (derived.hasLabelElement()) { 2680 if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false)) 2681 base.setLabelElement(mergeStrings(derived.getLabelElement(), base.getLabelElement())); 2682 else if (trimDifferential) 2683 base.setLabelElement(derived.getLabelElement().copy()); 2684 else if (derived.hasLabelElement()) 2685 derived.getLabelElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2686 } 2687 2688 if (derived.hasRequirementsElement()) { 2689 if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false)) 2690 base.setRequirementsElement(mergeMarkdown(derived.getRequirementsElement(), base.getRequirementsElement())); 2691 else if (trimDifferential) 2692 base.setRequirementsElement(derived.getRequirementsElement().copy()); 2693 else if (derived.hasRequirementsElement()) 2694 derived.getRequirementsElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2695 } 2696 // sdf-9 2697 if (derived.hasRequirements() && !base.getPath().contains(".")) 2698 derived.setRequirements(null); 2699 if (base.hasRequirements() && !base.getPath().contains(".")) 2700 base.setRequirements(null); 2701 2702 if (derived.hasAlias()) { 2703 if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false)) 2704 for (StringType s : derived.getAlias()) { 2705 if (!base.hasAlias(s.getValue())) 2706 base.getAlias().add(s.copy()); 2707 } 2708 else if (trimDifferential) 2709 derived.getAlias().clear(); 2710 else 2711 for (StringType t : derived.getAlias()) 2712 t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2713 } 2714 2715 if (derived.hasMinElement()) { 2716 if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { 2717 if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply 2718 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than the base min ("+Integer.toString(base.getMin())+") in "+srcSD.getVersionedUrl(), ValidationMessage.IssueSeverity.ERROR)); 2719 base.setMinElement(derived.getMinElement().copy()); 2720 } else if (trimDifferential) 2721 derived.setMinElement(null); 2722 else 2723 derived.getMinElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2724 } 2725 2726 if (derived.hasMaxElement()) { 2727 if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { 2728 if (isLargerMax(derived.getMax(), base.getMax())) 2729 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than the base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR)); 2730 base.setMaxElement(derived.getMaxElement().copy()); 2731 } else if (trimDifferential) 2732 derived.setMaxElement(null); 2733 else 2734 derived.getMaxElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2735 } 2736 2737 if (derived.hasFixed()) { 2738 if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) { 2739 base.setFixed(derived.getFixed().copy()); 2740 } else if (trimDifferential) 2741 derived.setFixed(null); 2742 else 2743 derived.getFixed().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2744 } 2745 2746 if (derived.hasPattern()) { 2747 if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) { 2748 base.setPattern(derived.getPattern().copy()); 2749 } else 2750 if (trimDifferential) 2751 derived.setPattern(null); 2752 else 2753 derived.getPattern().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2754 } 2755 2756 List<ElementDefinitionExampleComponent> toDelB = new ArrayList<>(); 2757 List<ElementDefinitionExampleComponent> toDelD = new ArrayList<>(); 2758 for (ElementDefinitionExampleComponent ex : derived.getExample()) { 2759 boolean delete = ex.hasExtension(ExtensionDefinitions.EXT_ED_SUPPRESS); 2760 if (delete && "$all".equals(ex.getLabel())) { 2761 toDelB.addAll(base.getExample()); 2762 } else { 2763 boolean found = false; 2764 for (ElementDefinitionExampleComponent exS : base.getExample()) { 2765 if (Base.compareDeep(ex.getLabel(), exS.getLabel(), false) && Base.compareDeep(ex.getValue(), exS.getValue(), false)) { 2766 if (delete) { 2767 toDelB.add(exS); 2768 } else { 2769 found = true; 2770 } 2771 } 2772 } 2773 if (delete) { 2774 toDelD.add(ex); 2775 } else if (!found) { 2776 base.addExample(ex.copy()); 2777 } else if (trimDifferential) { 2778 derived.getExample().remove(ex); 2779 } else { 2780 ex.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2781 } 2782 } 2783 } 2784 base.getExample().removeAll(toDelB); 2785 derived.getExample().removeAll(toDelD); 2786 2787 if (derived.hasMaxLengthElement()) { 2788 if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false)) 2789 base.setMaxLengthElement(derived.getMaxLengthElement().copy()); 2790 else if (trimDifferential) 2791 derived.setMaxLengthElement(null); 2792 else 2793 derived.getMaxLengthElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2794 } 2795 2796 if (derived.hasMaxValue()) { 2797 if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false)) 2798 base.setMaxValue(derived.getMaxValue().copy()); 2799 else if (trimDifferential) 2800 derived.setMaxValue(null); 2801 else 2802 derived.getMaxValue().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2803 } 2804 2805 if (derived.hasMinValue()) { 2806 if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false)) 2807 base.setMinValue(derived.getMinValue().copy()); 2808 else if (trimDifferential) 2809 derived.setMinValue(null); 2810 else 2811 derived.getMinValue().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2812 } 2813 2814 // todo: what to do about conditions? 2815 // condition : id 0..* 2816 2817 boolean hasMustSupport = derived.hasMustSupportElement(); 2818 for (ElementDefinition ed : obligationProfileElements) { 2819 hasMustSupport = hasMustSupport || ed.hasMustSupportElement(); 2820 } 2821 if (hasMustSupport) { 2822 BooleanType mse = derived.getMustSupportElement().copy(); 2823 for (ElementDefinition ed : obligationProfileElements) { 2824 mergeExtensions(mse, ed.getMustSupportElement()); 2825 if (ed.getMustSupport()) { 2826 mse.setValue(true); 2827 } 2828 } 2829 if (!(base.hasMustSupportElement() && Base.compareDeep(base.getMustSupportElement(), mse, false))) { 2830 if (base.hasMustSupport() && base.getMustSupport() && !derived.getMustSupport() && !fromSlicer) { 2831 addMessage(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)); 2832 } 2833 base.setMustSupportElement(mse); 2834 } else if (trimDifferential) 2835 derived.setMustSupportElement(null); 2836 else 2837 derived.getMustSupportElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2838 } 2839 2840 if (derived.hasMustHaveValueElement()) { 2841 if (!(base.hasMustHaveValueElement() && Base.compareDeep(derived.getMustHaveValueElement(), base.getMustHaveValueElement(), false))) { 2842 if (base.hasMustHaveValue() && base.getMustHaveValue() && !derived.getMustHaveValue() && !fromSlicer) { 2843 addMessage(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)); 2844 } 2845 base.setMustHaveValueElement(derived.getMustHaveValueElement().copy()); 2846 } else if (trimDifferential) 2847 derived.setMustHaveValueElement(null); 2848 else 2849 derived.getMustHaveValueElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2850 } 2851 if (derived.hasValueAlternatives()) { 2852 if (!Base.compareDeep(derived.getValueAlternatives(), base.getValueAlternatives(), false)) 2853 for (CanonicalType s : derived.getValueAlternatives()) { 2854 if (!base.hasValueAlternatives(s.getValue())) 2855 base.getValueAlternatives().add(s.copy()); 2856 } 2857 else if (trimDifferential) 2858 derived.getValueAlternatives().clear(); 2859 else 2860 for (CanonicalType t : derived.getValueAlternatives()) 2861 t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2862 } 2863 2864 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 2865 // but extensions can change isModifier 2866 if (isExtension) { 2867 if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) { 2868 base.setIsModifierElement(derived.getIsModifierElement().copy()); 2869 } else if (trimDifferential) { 2870 derived.setIsModifierElement(null); 2871 } else if (derived.hasIsModifierElement()) { 2872 derived.getIsModifierElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2873 } 2874 if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) { 2875 base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy()); 2876 } else if (trimDifferential) { 2877 derived.setIsModifierReasonElement(null); 2878 } else if (derived.hasIsModifierReasonElement()) { 2879 derived.getIsModifierReasonElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2880 } 2881 if (base.getIsModifier() && !base.hasIsModifierReason()) { 2882 // we get here because modifier extensions don't get a modifier reason from the type 2883 base.setIsModifierReason("Modifier extensions are labelled as such because they modify the meaning or interpretation of the resource or element that contains them"); 2884 } 2885 } 2886 2887 boolean hasBinding = derived.hasBinding(); 2888 for (ElementDefinition ed : obligationProfileElements) { 2889 hasBinding = hasBinding || ed.hasBinding(); 2890 } 2891 if (hasBinding) { 2892 updateExtensionsFromDefinition(dest.getBinding(), source.getBinding(), derivedSrc, srcSD); 2893 ElementDefinitionBindingComponent binding = derived.getBinding(); 2894 for (ElementDefinition ed : obligationProfileElements) { 2895 for (Extension ext : ed.getBinding().getExtension()) { 2896 if (ExtensionDefinitions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) { 2897 String p = ext.getExtensionString("purpose"); 2898 if (!Utilities.existsInList(p, "maximum", "required", "extensible")) { 2899 if (!binding.hasExtension(ext)) { 2900 binding.getExtension().add(ext.copy()); 2901 } 2902 } 2903 } 2904 } 2905 for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { 2906 if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) { 2907 if (binding.hasAdditional(ab)) { 2908 binding.getAdditional().add(ab.copy()); 2909 } 2910 } 2911 } 2912 } 2913 2914 if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 2915 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 2916 addMessage(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)); 2917// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 2918 else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) { 2919 ValueSet baseVs = context.findTxResource(ValueSet.class, base.getBinding().getValueSet(), null, srcSD); 2920 ValueSet contextVs = context.findTxResource(ValueSet.class, derived.getBinding().getValueSet(), null, derivedSrc); 2921 if (baseVs == null) { 2922 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 2923 } else if (contextVs == null) { 2924 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 2925 } else { 2926 ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false); 2927 ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false); 2928 if (expBase.getValueset() == null) 2929 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 2930 else if (expDerived.getValueset() == null) 2931 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 2932 else if (ExtensionUtilities.hasExtension(expBase.getValueset().getExpansion(), ExtensionDefinitions.EXT_EXP_TOOCOSTLY)) { 2933 if (ExtensionUtilities.hasExtension(expDerived.getValueset().getExpansion(), ExtensionDefinitions.EXT_EXP_TOOCOSTLY) || expDerived.getValueset().getExpansion().getContains().size() > 100) { 2934 addMessage(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)); 2935 } else { 2936 boolean ok = true; 2937 for (ValueSetExpansionContainsComponent cc : expDerived.getValueset().getExpansion().getContains()) { 2938 ValidationResult vr = context.validateCode(new ValidationOptions(), cc.getSystem(), cc.getVersion(), cc.getCode(), null, baseVs); 2939 if (!vr.isOk()) { 2940 ok = false; 2941 break; 2942 } 2943 } 2944 if (!ok) { 2945 addMessage(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)); 2946 } 2947 } 2948 } else if (expBase.getValueset().getExpansion().getContains().size() == 1000 || 2949 expDerived.getValueset().getExpansion().getContains().size() == 1000) { 2950 addMessage(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()+" - value set is too large to check", ValidationMessage.IssueSeverity.WARNING)); 2951 } else { 2952 String msgs = checkSubset(expBase.getValueset(), expDerived.getValueset()); 2953 if (msgs != null) { 2954 addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet()+" because "+msgs, ValidationMessage.IssueSeverity.ERROR)); 2955 } 2956 } 2957 } 2958 } 2959 ElementDefinitionBindingComponent d = derived.getBinding(); 2960 ElementDefinitionBindingComponent nb = base.getBinding().copy(); 2961 if (!COPY_BINDING_EXTENSIONS) { 2962 nb.getExtension().clear(); 2963 } 2964 nb.setDescription(null); 2965 for (Extension dex : d.getExtension()) { 2966 nb.getExtension().add(markExtensionSource(dex.copy(), false, srcSD)); 2967 } 2968 if (d.hasStrength()) { 2969 nb.setStrength(d.getStrength()); 2970 } 2971 if (d.hasDescription()) { 2972 nb.setDescription(d.getDescription()); 2973 } 2974 if (d.hasValueSet()) { 2975 nb.setValueSet(d.getValueSet()); 2976 } 2977 for (ElementDefinitionBindingAdditionalComponent ab : d.getAdditional()) { 2978 ElementDefinitionBindingAdditionalComponent eab = getMatchingAdditionalBinding(nb, ab); 2979 if (eab != null) { 2980 mergeAdditionalBinding(eab, ab); 2981 } else { 2982 nb.getAdditional().add(ab); 2983 } 2984 } 2985 base.setBinding(nb); 2986 } else if (trimDifferential) 2987 derived.setBinding(null); 2988 else 2989 derived.getBinding().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 2990 } else if (base.hasBinding()) { 2991 base.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS)); 2992 for (Extension ex : base.getBinding().getExtension()) { 2993 markExtensionSource(ex, false, srcSD); 2994 } 2995 } 2996 2997 if (derived.hasIsSummaryElement()) { 2998 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) { 2999 if (base.hasIsSummary() && !context.getVersion().equals("1.4.0")) // work around a known issue with some 1.4.0 cosntraints 3000 throw new Error(context.formatMessage(I18nConstants.ERROR_IN_PROFILE__AT__BASE_ISSUMMARY___DERIVED_ISSUMMARY__, purl, derived.getPath(), base.getIsSummaryElement().asStringValue(), derived.getIsSummaryElement().asStringValue())); 3001 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 3002 } else if (trimDifferential) 3003 derived.setIsSummaryElement(null); 3004 else 3005 derived.getIsSummaryElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 3006 } 3007 3008 // this would make sense but blows up the process later, so we let it happen anyway, and sort out the business rule elsewhere 3009 //if (!derived.hasContentReference() && !base.hasContentReference()) { 3010 3011 if (derived.hasType()) { 3012 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 3013 if (base.hasType()) { 3014 for (TypeRefComponent ts : derived.getType()) { 3015 checkTypeDerivation(purl, srcSD, base, derived, ts, path, derivedSrc.getDerivation() == TypeDerivationRule.SPECIALIZATION); 3016 } 3017 } 3018 base.getType().clear(); 3019 for (TypeRefComponent t : derived.getType()) { 3020 TypeRefComponent tt = t.copy(); 3021 // tt.setUserData(DERIVATION_EQUALS, true); 3022 base.getType().add(tt); 3023 for (Extension ex : tt.getExtension()) { 3024 markExtensionSource(ex, false, srcSD); 3025 } 3026 } 3027 } 3028 else if (trimDifferential) 3029 derived.getType().clear(); 3030 else { 3031 for (TypeRefComponent t : derived.getType()) { 3032 t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true); 3033 for (Extension ex : t.getExtension()) { 3034 markExtensionSource(ex, true, derivedSrc); 3035 } 3036 } 3037 } 3038 } 3039 3040 mappings.merge(derived, base); // note reversal of names to be correct in .merge() 3041 3042 // todo: constraints are cumulative. there is no replacing 3043 for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 3044 s.setUserData(UserDataNames.SNAPSHOT_IS_DERIVED, true); 3045 if (!s.hasSource()) { 3046 s.setSource(srcSD.getUrl()); 3047 } 3048 } 3049 if (derived.hasConstraint()) { 3050 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 3051 if (!base.hasConstraint(s.getKey())) { 3052 ElementDefinitionConstraintComponent inv = s.copy(); 3053 base.getConstraint().add(inv); 3054 } 3055 } 3056 } 3057 for (IdType id : derived.getCondition()) { 3058 if (!base.hasCondition(id)) { 3059 base.getCondition().add(id); 3060 } 3061 } 3062 3063 // now, check that we still have a bindable type; if not, delete the binding - see task 8477 3064 if (dest.hasBinding() && !hasBindableType(dest)) { 3065 dest.setBinding(null); 3066 } 3067 3068// // finally, we copy any extensions from source to dest 3069 //no, we already did. 3070// for (Extension ex : derived.getExtension()) { 3071// ! 3072// StructureDefinition sd = findProfile(ex.getUrl(), derivedSrc); 3073// if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) { 3074// ToolingExtensions.removeExtension(dest, ex.getUrl()); 3075// } 3076// dest.addExtension(ex.copy()); 3077// } 3078 } 3079 if (dest.hasFixed()) { 3080 checkTypeOk(dest, dest.getFixed().fhirType(), srcSD, "fixed"); 3081 } 3082 if (dest.hasPattern()) { 3083 checkTypeOk(dest, dest.getPattern().fhirType(), srcSD, "pattern"); 3084 } 3085 //updateURLs(url, webUrl, dest); 3086 } 3087 3088 private MarkdownType mergeMarkdown(MarkdownType dest, MarkdownType source) { 3089 MarkdownType mergedMarkdown = dest.copy(); 3090 if (!mergedMarkdown.hasValue() && source.hasValue()) { 3091 mergedMarkdown.setValue(source.getValue()); 3092 } else if (mergedMarkdown.hasValue() && source.hasValue() && mergedMarkdown.getValue().startsWith("...")) { 3093 mergedMarkdown.setValue(Utilities.appendDerivedTextToBase(source.getValue(), mergedMarkdown.getValue())); 3094 } 3095 for (Extension sourceExtension : source.getExtension()) { 3096 Extension matchingExtension = findMatchingExtension(mergedMarkdown, sourceExtension); 3097 if (matchingExtension == null) { 3098 mergedMarkdown.addExtension(sourceExtension.copy()); 3099 } else { 3100 matchingExtension.setValue(sourceExtension.getValue()); 3101 } 3102 } 3103 return mergedMarkdown; 3104 } 3105 3106 private StringType mergeStrings(StringType dest, StringType source) { 3107 StringType res = dest.copy(); 3108 if (!res.hasValue() && source.hasValue()) { 3109 res.setValue(source.getValue()); 3110 } else if (res.hasValue() && source.hasValue() && res.getValue().startsWith("...")) { 3111 res.setValue(Utilities.appendDerivedTextToBase(res.getValue(), source.getValue())); 3112 } 3113 for (Extension sourceExtension : source.getExtension()) { 3114 Extension matchingExtension = findMatchingExtension(res, sourceExtension); 3115 if (matchingExtension == null) { 3116 res.addExtension(sourceExtension.copy()); 3117 } else { 3118 matchingExtension.setValue(sourceExtension.getValue()); 3119 } 3120 } 3121 return res; 3122 } 3123 3124 private Extension findMatchingExtension(Element res, Extension extensionToMatch) { 3125 for (Extension elementExtension : res.getExtensionsByUrl(extensionToMatch.getUrl())) { 3126 if (ExtensionDefinitions.EXT_TRANSLATION.equals(elementExtension.getUrl())) { 3127 String slang = extensionToMatch.getExtensionString("lang"); 3128 String dlang = elementExtension.getExtensionString("lang"); 3129 if (Utilities.stringsEqual(slang, dlang)) { 3130 return elementExtension; 3131 } 3132 } else { 3133 return elementExtension; 3134 } 3135 3136 } 3137 return null; 3138 } 3139 3140 private static Extension markExtensionSource(Extension extension, boolean overrideSource, StructureDefinition srcSD) { 3141 if (overrideSource || !extension.hasUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE)) { 3142 extension.setUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE, srcSD); 3143 } 3144 if (Utilities.existsInList(extension.getUrl(), ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) { 3145 Extension sub = extension.getExtensionByUrl(ExtensionDefinitions.EXT_OBLIGATION_SOURCE, ExtensionDefinitions.EXT_OBLIGATION_SOURCE_SHORT); 3146 if (sub == null || overrideSource) { 3147 ExtensionUtilities.setCanonicalExtension(extension, ExtensionDefinitions.EXT_OBLIGATION_SOURCE, srcSD.getVersionedUrl()); 3148 } 3149 } 3150 return extension; 3151 } 3152 3153 private void updateExtensionsFromDefinition(Element dest, Element source, StructureDefinition destSD, StructureDefinition srcSD) { 3154 dest.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) || (Utilities.existsInList(ext.getUrl(), DEFAULT_INHERITED_ED_URLS) && source.hasExtension(ext.getUrl()))); 3155 3156 for (Extension ext : source.getExtension()) { 3157 if (!dest.hasExtension(ext.getUrl())) { 3158 dest.getExtension().add(markExtensionSource(ext.copy(), false, srcSD)); 3159 } else if (Utilities.existsInList(ext.getUrl(), NON_OVERRIDING_ED_URLS)) { 3160 // do nothing 3161 for (Extension ex2 : dest.getExtensionsByUrl(ext.getUrl())) { 3162 markExtensionSource(ex2, true, destSD); 3163 } 3164 } else if (Utilities.existsInList(ext.getUrl(), OVERRIDING_ED_URLS)) { 3165 dest.getExtensionByUrl(ext.getUrl()).setValue(ext.getValue()); 3166 markExtensionSource(dest.getExtensionByUrl(ext.getUrl()), false, srcSD); 3167 } else { 3168 dest.getExtension().add(markExtensionSource(ext.copy(), false, srcSD)); 3169 } 3170 } 3171 } 3172 3173 private void mergeAdditionalBinding(ElementDefinitionBindingAdditionalComponent dest, ElementDefinitionBindingAdditionalComponent source) { 3174 for (UsageContext t : source.getUsage()) { 3175 if (!hasUsage(dest, t)) { 3176 dest.addUsage(t); 3177 } 3178 } 3179 if (source.getAny()) { 3180 source.setAny(true); 3181 } 3182 if (source.hasShortDoco()) { 3183 dest.setShortDoco(source.getShortDoco()); 3184 } 3185 if (source.hasDocumentation()) { 3186 dest.setDocumentation(source.getDocumentation()); 3187 } 3188 3189 } 3190 3191 private boolean hasUsage(ElementDefinitionBindingAdditionalComponent dest, UsageContext tgt) { 3192 for (UsageContext t : dest.getUsage()) { 3193 if (t.getCode() != null && t.getCode().matches(tgt.getCode()) && t.getValue() != null && t.getValue().equals(tgt.getValue())) { 3194 return true; 3195 } 3196 } 3197 return false; 3198 } 3199 3200 private ElementDefinitionBindingAdditionalComponent getMatchingAdditionalBinding(ElementDefinitionBindingComponent nb,ElementDefinitionBindingAdditionalComponent ab) { 3201 for (ElementDefinitionBindingAdditionalComponent t : nb.getAdditional()) { 3202 if (t.getValueSet() != null && t.getValueSet().equals(ab.getValueSet()) && t.getPurpose() == ab.getPurpose() && !ab.hasUsage()) { 3203 return t; 3204 } 3205 } 3206 return null; 3207 } 3208 3209 private void mergeExtensions(Element tgt, Element src) { 3210 tgt.getExtension().addAll(src.getExtension()); 3211 } 3212 3213 private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts, String path, boolean specialising) { 3214 boolean ok = false; 3215 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3216 String t = ts.getWorkingCode(); 3217 String tDesc = ts.toString(); 3218 for (TypeRefComponent td : base.getType()) {; 3219 boolean matchType = false; 3220 String tt = td.getWorkingCode(); 3221 b.append(td.toString()); 3222 if (td.hasCode() && (tt.equals(t))) { 3223 matchType = true; 3224 } 3225 if (!matchType) { 3226 StructureDefinition sdt = context.fetchTypeDefinition(tt); 3227 if (sdt != null && (sdt.getAbstract() || sdt.getKind() == StructureDefinitionKind.LOGICAL)) { 3228 StructureDefinition sdb = context.fetchTypeDefinition(t); 3229 while (sdb != null && !matchType) { 3230 matchType = sdb.getType().equals(sdt.getType()); 3231 sdb = findProfile(sdb.getBaseDefinition(), sdb); 3232 } 3233 } 3234 } 3235 // work around for old badly generated SDs 3236// if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) { 3237// matchType = true; 3238// } 3239// if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) { 3240// matchType = true; 3241// } 3242 if (matchType) { 3243 ts.copyExtensions(td, "http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support", "http://hl7.org/fhir/StructureDefinition/elementdefinition-pattern", "http://hl7.org/fhir/StructureDefinition/obligation", "http://hl7.org/fhir/tools/StructureDefinition/obligation"); 3244 if (ts.hasTargetProfile()) { 3245 // check that any derived target has a reference chain back to one of the base target profiles 3246 for (UriType u : ts.getTargetProfile()) { 3247 String url = u.getValue(); 3248 boolean tgtOk = !td.hasTargetProfile() || sdConformsToTargets(path, derived.getPath(), url, td); 3249 if (tgtOk) { 3250 ok = true; 3251 } else if (specialising) { 3252 ok = true; 3253 } else { 3254 addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, derived.getPath(), context.formatMessage(I18nConstants.ERROR_AT__THE_TARGET_PROFILE__IS_NOT__VALID_CONSTRAINT_ON_THE_BASE_, purl, derived.getPath(), url, td.getTargetProfile()), IssueSeverity.ERROR)); 3255 } 3256 } 3257 } else { 3258 ok = true; 3259 } 3260 } 3261 } 3262 if (!ok && !isSuppressIgnorableExceptions()) { 3263 throw new DefinitionException(context.formatMessage(I18nConstants.STRUCTUREDEFINITION__AT__ILLEGAL_CONSTRAINED_TYPE__FROM__IN_, purl, derived.getPath(), tDesc, b.toString(), srcSD.getUrl())); 3264 } 3265 } 3266 3267 3268 private boolean sdConformsToTargets(String path, String dPath, String url, TypeRefComponent td) { 3269 if (td.hasTargetProfile(url)) { 3270 return true; 3271 } 3272 if (url != null && url.contains("|") && td.hasTargetProfile(url.substring(0, url.indexOf("|")))) { 3273 return true; 3274 } 3275 StructureDefinition sd = context.fetchResourceRaw(StructureDefinition.class, url); 3276 if (sd == null) { 3277 addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, "Cannot check whether the target profile " + url + " on "+dPath+" is valid constraint on the base because it is not known", IssueSeverity.WARNING)); 3278 return true; 3279 } else { 3280 if (sd.hasBaseDefinition() && sdConformsToTargets(path, dPath, sd.getBaseDefinition(), td)) { 3281 return true; 3282 } 3283 for (Extension ext : sd.getExtensionsByUrl(ExtensionDefinitions.EXT_SD_IMPOSE_PROFILE)) { 3284 if (sdConformsToTargets(path, dPath, ext.getValueCanonicalType().asStringValue(), td)) { 3285 return true; 3286 } 3287 } 3288 } 3289 return false; 3290 } 3291 3292 private void checkTypeOk(ElementDefinition dest, String ft, StructureDefinition sd, String fieldName) { 3293 boolean ok = false; 3294 Set<String> types = new HashSet<>(); 3295 if (dest.getPath().contains(".")) { 3296 for (TypeRefComponent t : dest.getType()) { 3297 if (t.hasCode()) { 3298 types.add(t.getWorkingCode()); 3299 } 3300 ok = ok || ft.equals(t.getWorkingCode()); 3301 } 3302 } else { 3303 types.add(sd.getType()); 3304 ok = ok || ft.equals(sd.getType()); 3305 3306 } 3307 if (!ok) { 3308 addMessage(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)); 3309 } 3310 } 3311 3312 private boolean hasBindableType(ElementDefinition ed) { 3313 for (TypeRefComponent tr : ed.getType()) { 3314 if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code", "CodeableReference")) { 3315 return true; 3316 } 3317 StructureDefinition sd = context.fetchTypeDefinition(tr.getCode()); 3318 if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_BINDING_STYLE)) { 3319 return true; 3320 } 3321 if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_TYPE_CHARACTERISTICS) && 3322 "can-bind".equals(ExtensionUtilities.readStringExtension(sd, ExtensionDefinitions.EXT_TYPE_CHARACTERISTICS))) { 3323 return true; 3324 } 3325 } 3326 return false; 3327 } 3328 3329 3330 private boolean isLargerMax(String derived, String base) { 3331 if ("*".equals(base)) { 3332 return false; 3333 } 3334 if ("*".equals(derived)) { 3335 return true; 3336 } 3337 return Integer.parseInt(derived) > Integer.parseInt(base); 3338 } 3339 3340 3341 private String checkSubset(ValueSet expBase, ValueSet expDerived) { 3342 Set<String> codes = new HashSet<>(); 3343 checkCodesInExpansion(codes, expDerived.getExpansion().getContains(), expBase.getExpansion()); 3344 if (codes.isEmpty()) { 3345 return null; 3346 } else { 3347 return "The codes '"+CommaSeparatedStringBuilder.join(",", codes)+"' are not in the base valueset"; 3348 } 3349 } 3350 3351 3352 private void checkCodesInExpansion(Set<String> codes, List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) { 3353 for (ValueSetExpansionContainsComponent cc : contains) { 3354 if (!inExpansion(cc, expansion.getContains())) { 3355 codes.add(cc.getCode()); 3356 } 3357 checkCodesInExpansion(codes, cc.getContains(), expansion); 3358 } 3359 } 3360 3361 3362 private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) { 3363 for (ValueSetExpansionContainsComponent cc1 : contains) { 3364 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) { 3365 return true; 3366 } 3367 if (inExpansion(cc, cc1.getContains())) { 3368 return true; 3369 } 3370 } 3371 return false; 3372 } 3373 3374 public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { 3375 for (ElementDefinition edb : base.getSnapshot().getElement()) { 3376 if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { 3377 ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); 3378 if (edm == null) { 3379 ElementDefinition edd = derived.getDifferential().addElement(); 3380 edd.setPath(edb.getPath()); 3381 edd.setMax("0"); 3382 } else if (edb.hasSlicing()) { 3383 closeChildren(base, edb, derived, edm); 3384 } 3385 } 3386 } 3387 sortDifferential(base, derived, derived.getName(), new ArrayList<String>(), false); 3388 } 3389 3390 private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) { 3391// String path = edb.getPath()+"."; 3392 int baseStart = base.getSnapshot().getElement().indexOf(edb); 3393 int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1); 3394 int diffStart = derived.getDifferential().getElement().indexOf(edm); 3395 int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1); 3396 3397 for (int cBase = baseStart; cBase < baseEnd; cBase++) { 3398 ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); 3399 if (isImmediateChild(edBase, edb)) { 3400 ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd); 3401 if (edMatch == null) { 3402 ElementDefinition edd = derived.getDifferential().addElement(); 3403 edd.setPath(edBase.getPath()); 3404 edd.setMax("0"); 3405 } else { 3406 closeChildren(base, edBase, derived, edMatch); 3407 } 3408 } 3409 } 3410 } 3411 3412 private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) { 3413 String path = ed.getPath()+"."; 3414 while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) { 3415 cursor++; 3416 } 3417 return cursor; 3418 } 3419 3420 3421 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) { 3422 for (ElementDefinition t : list) { 3423 if (t.getPath().equals(ed.getPath())) { 3424 return t; 3425 } 3426 } 3427 return null; 3428 } 3429 3430 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) { 3431 for (int i = start; i < end; i++) { 3432 ElementDefinition t = list.get(i); 3433 if (t.getPath().equals(ed.getPath())) { 3434 return t; 3435 } 3436 } 3437 return null; 3438 } 3439 3440 3441 private boolean isImmediateChild(ElementDefinition ed) { 3442 String p = ed.getPath(); 3443 if (!p.contains(".")) { 3444 return false; 3445 } 3446 p = p.substring(p.indexOf(".")+1); 3447 return !p.contains("."); 3448 } 3449 3450 private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { 3451 String p = candidate.getPath(); 3452 if (!p.contains(".")) 3453 return false; 3454 if (!p.startsWith(base.getPath()+".")) 3455 return false; 3456 p = p.substring(base.getPath().length()+1); 3457 return !p.contains("."); 3458 } 3459 3460 3461 3462 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 3463 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 3464 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 3465 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url")) 3466 return ed.getSnapshot().getElement().get(i); 3467 i++; 3468 } 3469 return null; 3470 } 3471 3472 3473 3474 protected ElementDefinitionResolution getElementById(StructureDefinition source, List<ElementDefinition> elements, String contentReference) { 3475 if (!contentReference.startsWith("#") && contentReference.contains("#")) { 3476 String url = contentReference.substring(0, contentReference.indexOf("#")); 3477 contentReference = contentReference.substring(contentReference.indexOf("#")); 3478 if (!url.equals(source.getUrl())){ 3479 source = findProfile(url, source); 3480 if (source == null) { 3481 return null; 3482 } 3483 elements = source.getSnapshot().getElement(); 3484 } 3485 } 3486 for (ElementDefinition ed : elements) 3487 if (ed.hasId() && ("#"+ed.getId()).equals(contentReference)) 3488 return new ElementDefinitionResolution(source, ed); 3489 return null; 3490 } 3491 3492 3493 public static String describeExtensionContext(StructureDefinition ext) { 3494 StringBuilder b = new StringBuilder(); 3495 b.append("Use on "); 3496 for (int i = 0; i < ext.getContext().size(); i++) { 3497 StructureDefinitionContextComponent ec = ext.getContext().get(i); 3498 if (i > 0) 3499 b.append(i < ext.getContext().size() - 1 ? ", " : " or "); 3500 b.append(ec.getType().getDisplay()); 3501 b.append(" "); 3502 b.append(ec.getExpression()); 3503 } 3504 if (ext.hasContextInvariant()) { 3505 b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = "); 3506 boolean first = true; 3507 for (StringType s : ext.getContextInvariant()) { 3508 if (first) 3509 first = false; 3510 else 3511 b.append(", "); 3512 b.append("<code>"+s.getValue()+"</code>"); 3513 } 3514 } 3515 return b.toString(); 3516 } 3517 3518 3519 3520 3521// public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, 3522// boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean mustSupport, RenderingContext rc) throws IOException, FHIRException { 3523// return generateTable(defFile, profile, diff, imageFolder, inlineGraphics, profileBaseFileName, snapshot, corePath, imagePath, logicalModel, allInvariants, outputTracker, mustSupport, rc, ""); 3524// } 3525 3526 3527 3528 3529 3530 protected String tail(String path) { 3531 if (path == null) { 3532 return ""; 3533 } else if (path.contains(".")) 3534 return path.substring(path.lastIndexOf('.')+1); 3535 else 3536 return path; 3537 } 3538 3539 private boolean isDataType(String value) { 3540 StructureDefinition sd = context.fetchTypeDefinition(value); 3541 if (sd == null) // might be running before all SDs are available 3542 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", 3543 "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext"); 3544 else 3545 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 3546 } 3547 3548 private boolean isConstrainedDataType(String value) { 3549 StructureDefinition sd = context.fetchTypeDefinition(value); 3550 if (sd == null) // might be running before all SDs are available 3551 return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"); 3552 else 3553 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT; 3554 } 3555 3556 private String baseType(String value) { 3557 StructureDefinition sd = context.fetchTypeDefinition(value); 3558 if (sd != null) // might be running before all SDs are available 3559 return sd.getTypeName(); 3560 if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity")) 3561 return "Quantity"; 3562 throw new Error(context.formatMessage(I18nConstants.INTERNAL_ERROR___TYPE_NOT_KNOWN_, value)); 3563 } 3564 3565 3566 protected boolean isPrimitive(String value) { 3567 StructureDefinition sd = context.fetchTypeDefinition(value); 3568 if (sd == null) // might be running before all SDs are available 3569 return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid"); 3570 else 3571 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 3572 } 3573 3574// private static String listStructures(StructureDefinition p) { 3575// StringBuilder b = new StringBuilder(); 3576// boolean first = true; 3577// for (ProfileStructureComponent s : p.getStructure()) { 3578// if (first) 3579// first = false; 3580// else 3581// b.append(", "); 3582// if (pkp != null && pkp.hasLinkFor(s.getType())) 3583// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 3584// else 3585// b.append(s.getType()); 3586// } 3587// return b.toString(); 3588// } 3589 3590 3591 public StructureDefinition getProfile(StructureDefinition source, String url) { 3592 StructureDefinition profile = null; 3593 String code = null; 3594 if (url.startsWith("#")) { 3595 profile = source; 3596 code = url.substring(1); 3597 } else if (context != null) { 3598 String[] parts = url.split("\\#"); 3599 profile = findProfile(parts[0], source); 3600 code = parts.length == 1 ? null : parts[1]; 3601 } 3602 if (profile == null) 3603 return null; 3604 if (code == null) 3605 return profile; 3606 for (Resource r : profile.getContained()) { 3607 if (r instanceof StructureDefinition && r.getId().equals(code)) 3608 return (StructureDefinition) r; 3609 } 3610 return null; 3611 } 3612 3613 3614 3615 private static class ElementDefinitionHolder { 3616 private String name; 3617 private ElementDefinition self; 3618 private int baseIndex = 0; 3619 private List<ElementDefinitionHolder> children; 3620 private boolean placeHolder = false; 3621 3622 public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) { 3623 super(); 3624 this.self = self; 3625 this.name = self.getPath(); 3626 this.placeHolder = isPlaceholder; 3627 children = new ArrayList<ElementDefinitionHolder>(); 3628 } 3629 3630 public ElementDefinitionHolder(ElementDefinition self) { 3631 this(self, false); 3632 } 3633 3634 public ElementDefinition getSelf() { 3635 return self; 3636 } 3637 3638 public List<ElementDefinitionHolder> getChildren() { 3639 return children; 3640 } 3641 3642 public int getBaseIndex() { 3643 return baseIndex; 3644 } 3645 3646 public void setBaseIndex(int baseIndex) { 3647 this.baseIndex = baseIndex; 3648 } 3649 3650 public boolean isPlaceHolder() { 3651 return this.placeHolder; 3652 } 3653 3654 @Override 3655 public String toString() { 3656 if (self.hasSliceName()) 3657 return self.getPath()+"("+self.getSliceName()+")"; 3658 else 3659 return self.getPath(); 3660 } 3661 } 3662 3663 private static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 3664 3665 private boolean inExtension; 3666 private StructureDefinition src; 3667 private List<ElementDefinition> snapshot; 3668 private int prefixLength; 3669 private String base; 3670 private String name; 3671 private String baseName; 3672 private Set<String> errors = new HashSet<String>(); 3673 3674 public ElementDefinitionComparer(boolean inExtension, StructureDefinition src, List<ElementDefinition> snapshot, String base, int prefixLength, String name, String baseName) { 3675 this.inExtension = inExtension; 3676 this.src = src; 3677 this.snapshot = snapshot; 3678 this.prefixLength = prefixLength; 3679 this.base = base; 3680 if (Utilities.isAbsoluteUrl(base)) { 3681 this.base = urlTail(base); 3682 } 3683 this.name = name; 3684 this.baseName = baseName; 3685 } 3686 3687 @Override 3688 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 3689 if (o1.getBaseIndex() == 0) { 3690 o1.setBaseIndex(find(o1.getSelf().getPath(), true)); 3691 } 3692 if (o2.getBaseIndex() == 0) { 3693 o2.setBaseIndex(find(o2.getSelf().getPath(), true)); 3694 } 3695 return o1.getBaseIndex() - o2.getBaseIndex(); 3696 } 3697 3698 private int find(String path, boolean mandatory) { 3699 String op = path; 3700 int lc = 0; 3701 String actual = base+path.substring(prefixLength); 3702 for (int i = 0; i < snapshot.size(); i++) { 3703 String p = snapshot.get(i).getPath(); 3704 if (p.equals(actual)) { 3705 return i; 3706 } 3707 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) { 3708 return i; 3709 } 3710 if (actual.endsWith("[x]") && p.startsWith(actual.substring(0, actual.length()-3)) && !p.substring(actual.length()-3).contains(".")) { 3711 return i; 3712 } 3713 if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) { 3714 String ref = snapshot.get(i).getContentReference(); 3715 if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) { 3716 actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3717 path = actual; 3718 } else if (ref.startsWith("http:")) { 3719 actual = base+(ref.substring(ref.indexOf("#")+1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3720 path = actual; 3721 } else { 3722 // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that 3723 actual = base+(path.substring(0, path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3724 path = actual; 3725 } 3726 3727 i = 0; 3728 lc++; 3729 if (lc > MAX_RECURSION_LIMIT) 3730 throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")"); 3731 } 3732 } 3733 if (mandatory) { 3734 if (prefixLength == 0) 3735 errors.add("Differential contains path "+path+" which is not found in the base "+baseName); 3736 else 3737 errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+ baseName); 3738 } 3739 return 0; 3740 } 3741 3742 public void checkForErrors(List<String> errorList) { 3743 if (errors.size() > 0) { 3744// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3745// for (String s : errors) 3746// b.append("StructureDefinition "+name+": "+s); 3747// throw new DefinitionException(b.toString()); 3748 for (String s : errors) 3749 if (s.startsWith("!")) 3750 errorList.add("!StructureDefinition "+name+": "+s.substring(1)); 3751 else 3752 errorList.add("StructureDefinition "+name+": "+s); 3753 } 3754 } 3755 } 3756 3757 3758 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors, boolean errorIfChanges) throws FHIRException { 3759 int index = 0; 3760 for (ElementDefinition ed : diff.getDifferential().getElement()) { 3761 ed.setUserData(UserDataNames.SNAPSHOT_SORT_ed_index, Integer.toString(index)); 3762 index++; 3763 } 3764 List<ElementDefinition> original = new ArrayList<>(); 3765 original.addAll(diff.getDifferential().getElement()); 3766 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 3767 int lastCount = diffList.size(); 3768 // first, we move the differential elements into a tree 3769 if (diffList.isEmpty()) 3770 return; 3771 3772 ElementDefinitionHolder edh = null; 3773 int i = 0; 3774 if (diffList.get(0).getPath().contains(".")) { 3775 String newPath = diffList.get(0).getPath().split("\\.")[0]; 3776 ElementDefinition e = new ElementDefinition(newPath); 3777 edh = new ElementDefinitionHolder(e, true); 3778 } else { 3779 edh = new ElementDefinitionHolder(diffList.get(0)); 3780 i = 1; 3781 } 3782 3783 boolean hasSlicing = false; 3784 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 3785 for(ElementDefinition elt : diffList) { 3786 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 3787 hasSlicing = true; 3788 break; 3789 } 3790 paths.add(elt.getPath()); 3791 } 3792 3793 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 3794 3795 // now, we sort the siblings throughout the tree 3796 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base, base.getSnapshot().getElement(), "", 0, name, base.getType()); 3797 sortElements(edh, cmp, errors); 3798 3799 // now, we serialise them back to a list 3800 List<ElementDefinition> newDiff = new ArrayList<>(); 3801 writeElements(edh, newDiff); 3802 if (errorIfChanges) { 3803 compareDiffs(original, newDiff, errors); 3804 } 3805 diffList.clear(); 3806 diffList.addAll(newDiff); 3807 3808 if (lastCount != diffList.size()) 3809 errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal"); 3810 } 3811 3812 private void compareDiffs(List<ElementDefinition> diffList, List<ElementDefinition> newDiff, List<String> errors) { 3813 if (diffList.size() != newDiff.size()) { 3814 errors.add("The diff list size changed when sorting - was "+diffList.size()+" is now "+newDiff.size()+ 3815 " ["+CommaSeparatedStringBuilder.buildObjects(diffList)+"]/["+CommaSeparatedStringBuilder.buildObjects(newDiff)+"]"); 3816 } else { 3817 for (int i = 0; i < Integer.min(diffList.size(), newDiff.size()); i++) { 3818 ElementDefinition e = diffList.get(i); 3819 ElementDefinition n = newDiff.get(i); 3820 if (!n.getPath().equals(e.getPath())) { 3821 errors.add("The element "+(e.hasId() ? e.getId() : e.getPath())+" @diff["+e.getUserString(UserDataNames.SNAPSHOT_SORT_ed_index)+"] is out of order (and maybe others after it)"); 3822 return; 3823 } 3824 } 3825 } 3826 } 3827 3828 3829 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 3830 String path = edh.getSelf().getPath(); 3831 final String prefix = path + "."; 3832 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 3833 if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) { 3834 String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0]; 3835 ElementDefinition e = new ElementDefinition(newPath); 3836 ElementDefinitionHolder child = new ElementDefinitionHolder(e, true); 3837 edh.getChildren().add(child); 3838 i = processElementsIntoTree(child, i, list); 3839 3840 } else { 3841 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 3842 edh.getChildren().add(child); 3843 i = processElementsIntoTree(child, i+1, list); 3844 } 3845 } 3846 return i; 3847 } 3848 3849 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException { 3850 if (edh.getChildren().size() == 1) 3851 // 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 3852 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false); 3853 else 3854 Collections.sort(edh.getChildren(), cmp); 3855 if (debug) { 3856 cmp.checkForErrors(errors); 3857 } 3858 3859 for (ElementDefinitionHolder child : edh.getChildren()) { 3860 if (child.getChildren().size() > 0) { 3861 ElementDefinitionComparer ccmp = getComparer(cmp, child); 3862 if (ccmp != null) { 3863 sortElements(child, ccmp, errors); 3864 } 3865 } 3866 } 3867 } 3868 3869 public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error { 3870 // what we have to check for here is running off the base profile into a data type profile 3871 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 3872 ElementDefinitionComparer ccmp; 3873 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) { 3874 if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && (child.getSelf().hasType() && child.getSelf().getType().get(0).hasProfile())) { 3875 if (child.getSelf().getType().get(0).getProfile().size() > 1) { 3876 throw new FHIRException(context.formatMessage(I18nConstants.UNHANDLED_SITUATION_RESOURCE_IS_PROFILED_TO_MORE_THAN_ONE_OPTION__CANNOT_SORT_PROFILE)); 3877 } 3878 StructureDefinition profile = findProfile(child.getSelf().getType().get(0).getProfile().get(0).getValue(), cmp.src); 3879 while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 3880 profile = findProfile(profile.getBaseDefinition(), profile); 3881 } 3882 if (profile==null) { 3883 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 3884 } else { 3885 ccmp = new ElementDefinitionComparer(true, profile, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3886 } 3887 } else { 3888 ccmp = new ElementDefinitionComparer(true, cmp.src, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name, cmp.name); 3889 } 3890 } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) { 3891 StructureDefinition profile = findProfile(child.getSelf().getType().get(0).getProfile().get(0).getValue(), cmp.src); 3892 if (profile==null) 3893 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 3894 else 3895 ccmp = new ElementDefinitionComparer(true, profile, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode(), cmp.src), child.getSelf().getPath().length(), cmp.name, profile.present()); 3896 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) { 3897 StructureDefinition profile = findProfile(sdNs(ed.getType().get(0).getWorkingCode()), cmp.src); 3898 if (profile==null) 3899 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 3900 ccmp = new ElementDefinitionComparer(false, profile, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode(), cmp.src), child.getSelf().getPath().length(), cmp.name, profile.present()); 3901 } else if (child.getSelf().getType().size() == 1) { 3902 StructureDefinition profile = findProfile(sdNs(child.getSelf().getType().get(0).getWorkingCode()), cmp.src); 3903 if (profile==null) 3904 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 3905 ccmp = new ElementDefinitionComparer(false, profile, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3906 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 3907 String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3908 String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3909 String p = childLastNode.substring(edLastNode.length()-3); 3910 if (isPrimitive(Utilities.uncapitalize(p))) 3911 p = Utilities.uncapitalize(p); 3912 StructureDefinition sd = findProfile(sdNs(p), cmp.src); 3913 if (sd == null) 3914 throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId())); 3915 ccmp = new ElementDefinitionComparer(false, sd, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name, sd.present()); 3916 } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) { 3917 for (TypeRefComponent t: child.getSelf().getType()) { 3918 if (!t.getWorkingCode().equals("Reference")) { 3919 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()))); 3920 } 3921 } 3922 StructureDefinition profile = findProfile(sdNs(ed.getType().get(0).getWorkingCode()), cmp.src); 3923 ccmp = new ElementDefinitionComparer(false, profile, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3924 } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) { 3925 for (TypeRefComponent t: ed.getType()) { 3926 if (!t.getWorkingCode().equals("Reference")) { 3927 throw new Error(context.formatMessage(I18nConstants.NOT_HANDLED_YET_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType()))); 3928 } 3929 } 3930 StructureDefinition profile = findProfile(sdNs(ed.getType().get(0).getWorkingCode()), cmp.src); 3931 ccmp = new ElementDefinitionComparer(false, profile, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present()); 3932 } else { 3933 // this is allowed if we only profile the extensions 3934 StructureDefinition profile = findProfile(sdNs("Element"), cmp.src); 3935 if (profile==null) 3936 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 3937 ccmp = new ElementDefinitionComparer(false, profile, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name, profile.present()); 3938// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3939 } 3940 return ccmp; 3941 } 3942 3943 private String resolveType(String code, StructureDefinition src) { 3944 if (Utilities.isAbsoluteUrl(code)) { 3945 StructureDefinition sd = findProfile(code, src); 3946 if (sd != null) { 3947 return sd.getType(); 3948 } 3949 } 3950 return code; 3951 } 3952 3953 private static String sdNs(String type) { 3954 return sdNs(type, null); 3955 } 3956 3957 public static String sdNs(String type, String overrideVersionNs) { 3958 if (Utilities.isAbsoluteUrl(type)) 3959 return type; 3960 else if (overrideVersionNs != null) 3961 return Utilities.pathURL(overrideVersionNs, type); 3962 else 3963 return "http://hl7.org/fhir/StructureDefinition/"+type; 3964 } 3965 3966 3967 private boolean isAbstract(String code) { 3968 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource"); 3969 } 3970 3971 3972 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 3973 if (!edh.isPlaceHolder()) 3974 list.add(edh.getSelf()); 3975 for (ElementDefinitionHolder child : edh.getChildren()) { 3976 writeElements(child, list); 3977 } 3978 } 3979 3980 /** 3981 * First compare element by path then by name if same 3982 */ 3983 private static class ElementNameCompare implements Comparator<ElementDefinition> { 3984 3985 @Override 3986 public int compare(ElementDefinition o1, ElementDefinition o2) { 3987 String path1 = normalizePath(o1); 3988 String path2 = normalizePath(o2); 3989 int cmp = path1.compareTo(path2); 3990 if (cmp == 0) { 3991 String name1 = o1.hasSliceName() ? o1.getSliceName() : ""; 3992 String name2 = o2.hasSliceName() ? o2.getSliceName() : ""; 3993 cmp = name1.compareTo(name2); 3994 } 3995 return cmp; 3996 } 3997 3998 private static String normalizePath(ElementDefinition e) { 3999 if (!e.hasPath()) return ""; 4000 String path = e.getPath(); 4001 // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc. 4002 // so strip off the [x] suffix when comparing the path names. 4003 if (path.endsWith("[x]")) { 4004 path = path.substring(0, path.length()-3); 4005 } 4006 return path; 4007 } 4008 4009 } 4010 4011 4012 // generate schematrons for the rules in a structure definition 4013 public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException { 4014 if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT) 4015 throw new DefinitionException(context.formatMessage(I18nConstants.NOT_THE_RIGHT_KIND_OF_STRUCTURE_TO_GENERATE_SCHEMATRONS_FOR)); 4016 if (!structure.hasSnapshot()) 4017 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 4018 4019 StructureDefinition base = findProfile(structure.getBaseDefinition(), structure); 4020 4021 if (base != null) { 4022 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 4023 4024 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 4025 generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base); 4026 sch.dump(); 4027 } 4028 } 4029 4030 public StructureDefinition findProfile(String url, Resource source) { 4031 if (url == null) { 4032 return null; 4033 } 4034 String u = url; 4035 String v = null; 4036 if (url.contains("|")) { 4037 v = url.substring(u.indexOf("|")+1); 4038 u = u.substring(0, u.indexOf("|")); 4039 } 4040 if (parameters != null) { 4041 if (v == null) { 4042 for (Parameters.ParametersParameterComponent p : parameters.getParameter()) { 4043 if ("default-profile-version".equals(p.getName())) { 4044 String s = p.getValue().primitiveValue(); 4045 if (s.startsWith(u + "|")) { 4046 v = s.substring(s.indexOf("|") + 1); 4047 } 4048 } 4049 } 4050 } 4051 for (Parameters.ParametersParameterComponent p : parameters.getParameter()) { 4052 if ("force-profile-version".equals(p.getName())) { 4053 String s = p.getValue().primitiveValue(); 4054 if (s.startsWith(u + "|")) { 4055 v = s.substring(s.indexOf("|") + 1); 4056 } 4057 } 4058 } 4059 for (Parameters.ParametersParameterComponent p : parameters.getParameter()) { 4060 if ("check-profile-version".equals(p.getName())) { 4061 String s = p.getValue().primitiveValue(); 4062 if (s.startsWith(u + "|")) { 4063 String vc = s.substring(s.indexOf("|") + 1); 4064 if (!vc.equals(v)) { 4065 throw new FHIRException("Profile resolves to " + v + " which does not match required profile version v" + vc); 4066 } 4067 } 4068 } 4069 } 4070 } 4071 // switch the extension pack in 4072 if (source != null && source.getSourcePackage() != null && source.getSourcePackage().isCore()) { 4073 source = null; 4074 } 4075 return context.fetchResource(StructureDefinition.class, u, v, source); 4076 } 4077 4078 // generate a CSV representation of the structure definition 4079 public void generateCsv(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception { 4080 if (!structure.hasSnapshot()) 4081 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 4082 4083 CSVWriter csv = new CSVWriter(dest, structure, asXml); 4084 4085 for (ElementDefinition child : structure.getSnapshot().getElement()) { 4086 csv.processElement(null, child); 4087 } 4088 csv.dump(); 4089 } 4090 4091 // generate a CSV representation of the structure definition 4092 public void addToCSV(CSVWriter csv, StructureDefinition structure) throws IOException, DefinitionException, Exception { 4093 if (!structure.hasSnapshot()) 4094 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 4095 4096 for (ElementDefinition child : structure.getSnapshot().getElement()) { 4097 csv.processElement(structure, child); 4098 } 4099 } 4100 4101 4102 private class Slicer extends ElementDefinitionSlicingComponent { 4103 String criteria = ""; 4104 String name = ""; 4105 boolean check; 4106 public Slicer(boolean cantCheck) { 4107 super(); 4108 this.check = cantCheck; 4109 } 4110 } 4111 4112 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) { 4113 // given a child in a structure, it's sliced. figure out the slicing xpath 4114 if (child.getPath().endsWith(".extension")) { 4115 ElementDefinition ued = getUrlFor(structure, child); 4116 if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile()))) 4117 return new Slicer(false); 4118 else { 4119 Slicer s = new Slicer(true); 4120 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue(); 4121 s.name = " with URL = '"+url+"'"; 4122 s.criteria = "[@url = '"+url+"']"; 4123 return s; 4124 } 4125 } else 4126 return new Slicer(false); 4127 } 4128 4129 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException { 4130 // generateForChild(txt, structure, child); 4131 List<ElementDefinition> children = getChildList(structure, ed); 4132 String sliceName = null; 4133 ElementDefinitionSlicingComponent slicing = null; 4134 for (ElementDefinition child : children) { 4135 String name = tail(child.getPath()); 4136 if (child.hasSlicing()) { 4137 sliceName = name; 4138 slicing = child.getSlicing(); 4139 } else if (!name.equals(sliceName)) 4140 slicing = null; 4141 4142 ElementDefinition based = getByPath(base, child.getPath()); 4143 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 4144 boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax()))); 4145 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 4146 if (slicer.check) { 4147 if (doMin || doMax) { 4148 Section s = sch.section(xpath); 4149 Rule r = s.rule(xpath); 4150 if (doMin) 4151 r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin())); 4152 if (doMax) 4153 r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax()); 4154 } 4155 } 4156 } 4157/// xpath has been removed 4158// for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 4159// if (inv.hasXpath()) { 4160// Section s = sch.section(ed.getPath()); 4161// Rule r = s.rule(xpath); 4162// r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 4163// } 4164// } 4165 if (!ed.hasContentReference()) { 4166 for (ElementDefinition child : children) { 4167 String name = tail(child.getPath()); 4168 generateForChildren(sch, xpath+"/f:"+name, child, structure, base); 4169 } 4170 } 4171 } 4172 4173 4174 4175 4176 private ElementDefinition getByPath(StructureDefinition base, String path) { 4177 for (ElementDefinition ed : base.getSnapshot().getElement()) { 4178 if (ed.getPath().equals(path)) 4179 return ed; 4180 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))) 4181 return ed; 4182 } 4183 return null; 4184 } 4185 4186 4187 public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException { 4188 if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { 4189 if (!sd.hasDifferential()) 4190 sd.setDifferential(new StructureDefinitionDifferentialComponent()); 4191 generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType(), sd); 4192 } 4193 if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { 4194 if (!sd.hasSnapshot()) 4195 sd.setSnapshot(new StructureDefinitionSnapshotComponent()); 4196 generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType(), sd); 4197 } 4198 } 4199 4200 4201 private boolean hasMissingIds(List<ElementDefinition> list) { 4202 for (ElementDefinition ed : list) { 4203 if (!ed.hasId()) 4204 return true; 4205 } 4206 return false; 4207 } 4208 4209 private class SliceList { 4210 4211 private Map<String, String> slices = new HashMap<>(); 4212 4213 public void seeElement(ElementDefinition ed) { 4214 Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator(); 4215 while (iter.hasNext()) { 4216 Map.Entry<String,String> entry = iter.next(); 4217 if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath())) 4218 iter.remove(); 4219 } 4220 4221 if (ed.hasSliceName()) 4222 slices.put(ed.getPath(), ed.getSliceName()); 4223 } 4224 4225 public String[] analyse(List<String> paths) { 4226 String s = paths.get(0); 4227 String[] res = new String[paths.size()]; 4228 res[0] = null; 4229 for (int i = 1; i < paths.size(); i++) { 4230 s = s + "."+paths.get(i); 4231 if (slices.containsKey(s)) 4232 res[i] = slices.get(s); 4233 else 4234 res[i] = null; 4235 } 4236 return res; 4237 } 4238 4239 } 4240 4241 protected void generateIds(List<ElementDefinition> list, String name, String type, StructureDefinition srcSD) throws DefinitionException { 4242 if (list.isEmpty()) 4243 return; 4244 4245 Map<String, String> idList = new HashMap<String, String>(); 4246 Map<String, String> replacedIds = new HashMap<String, String>(); 4247 4248 SliceList sliceInfo = new SliceList(); 4249 // first pass, update the element ids 4250 for (ElementDefinition ed : list) { 4251 generateIdForElement(list, name, type, srcSD, ed, sliceInfo, replacedIds, idList); 4252 } 4253 // second path - fix up any broken path based id references 4254 4255 } 4256 4257 private void generateIdForElement(List<ElementDefinition> list, String name, String type, StructureDefinition srcSD, ElementDefinition ed, SliceList sliceInfo, Map<String, String> replacedIds, Map<String, String> idList) { 4258 List<String> paths = new ArrayList<String>(); 4259 if (!ed.hasPath()) 4260 throw new DefinitionException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_DEFINITION__IN_, Integer.toString(list.indexOf(ed)), name)); 4261 sliceInfo.seeElement(ed); 4262 String[] pl = ed.getPath().split("\\."); 4263 for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus 4264 paths.add(pl[i]); 4265 String slices[] = sliceInfo.analyse(paths); 4266 4267 StringBuilder b = new StringBuilder(); 4268 b.append(paths.get(0)); 4269 for (int i = 1; i < paths.size(); i++) { 4270 b.append("."); 4271 String s = paths.get(i); 4272 String p = slices[i]; 4273 b.append(fixChars(s)); 4274 if (p != null) { 4275 b.append(":"); 4276 b.append(p); 4277 } 4278 } 4279 String bs = b.toString(); 4280 if (ed.hasId()) { 4281 replacedIds.put(ed.getId(), ed.getPath()); 4282 } 4283 ed.setId(bs); 4284 if (idList.containsKey(bs)) { 4285 addMessage(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, name +"."+bs, context.formatMessage(I18nConstants.SAME_ID_ON_MULTIPLE_ELEMENTS__IN_, bs, idList.get(bs), ed.getPath(), name), IssueSeverity.ERROR)); 4286 } 4287 idList.put(bs, ed.getPath()); 4288 if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) { 4289 String s = ed.getContentReference(); 4290 String typeURL = getUrlForSource(type, srcSD); 4291 if (replacedIds.containsKey(s.substring(1))) { 4292 ed.setContentReference(typeURL+"#"+ replacedIds.get(s.substring(1))); 4293 } else { 4294 ed.setContentReference(typeURL+s); 4295 } 4296 } 4297 } 4298 4299 4300 private String getUrlForSource(String type, StructureDefinition srcSD) { 4301 if (srcSD.getKind() == StructureDefinitionKind.LOGICAL) { 4302 return srcSD.getUrl(); 4303 } else { 4304 return "http://hl7.org/fhir/StructureDefinition/"+type; 4305 } 4306 } 4307 4308 private Object fixChars(String s) { 4309 return s.replace("_", "-"); 4310 } 4311 4312 4313// private String describeExtension(ElementDefinition ed) { 4314// if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) 4315// return ""; 4316// return "$"+urlTail(ed.getTypeFirstRep().getProfile()); 4317// } 4318// 4319 4320 private static String urlTail(String profile) { 4321 return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile; 4322 } 4323// 4324// 4325// private String checkName(String name) { 4326//// if (name.contains(".")) 4327////// throw new Exception("Illegal name "+name+": no '.'"); 4328//// if (name.contains(" ")) 4329//// throw new Exception("Illegal name "+name+": no spaces"); 4330// StringBuilder b = new StringBuilder(); 4331// for (char c : name.toCharArray()) { 4332// if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']')) 4333// b.append(c); 4334// } 4335// return b.toString().toLowerCase(); 4336// } 4337// 4338// 4339// private int charCount(String path, char t) { 4340// int res = 0; 4341// for (char ch : path.toCharArray()) { 4342// if (ch == t) 4343// res++; 4344// } 4345// return res; 4346// } 4347 4348// 4349//private void generateForChild(TextStreamWriter txt, 4350// StructureDefinition structure, ElementDefinition child) { 4351// // TODO Auto-generated method stub 4352// 4353//} 4354 4355 private interface ExampleValueAccessor { 4356 DataType getExampleValue(ElementDefinition ed); 4357 String getId(); 4358 } 4359 4360 private class BaseExampleValueAccessor implements ExampleValueAccessor { 4361 @Override 4362 public DataType getExampleValue(ElementDefinition ed) { 4363 if (ed.hasFixed()) 4364 return ed.getFixed(); 4365 if (ed.hasExample()) 4366 return ed.getExample().get(0).getValue(); 4367 else 4368 return null; 4369 } 4370 4371 @Override 4372 public String getId() { 4373 return "-genexample"; 4374 } 4375 } 4376 4377 private class ExtendedExampleValueAccessor implements ExampleValueAccessor { 4378 private String index; 4379 4380 public ExtendedExampleValueAccessor(String index) { 4381 this.index = index; 4382 } 4383 @Override 4384 public DataType getExampleValue(ElementDefinition ed) { 4385 if (ed.hasFixed()) 4386 return ed.getFixed(); 4387 for (Extension ex : ed.getExtension()) { 4388 String ndx = ExtensionUtilities.readStringExtension(ex, "index"); 4389 DataType value = ExtensionUtilities.getExtension(ex, "exValue").getValue(); 4390 if (index.equals(ndx) && value != null) 4391 return value; 4392 } 4393 return null; 4394 } 4395 @Override 4396 public String getId() { 4397 return "-genexample-"+index; 4398 } 4399 } 4400 4401 public List<org.hl7.fhir.r5.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException { 4402 List<org.hl7.fhir.r5.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r5.elementmodel.Element>(); 4403 if (sd.hasSnapshot()) { 4404 if (evenWhenNoExamples || hasAnyExampleValues(sd)) 4405 examples.add(generateExample(sd, new BaseExampleValueAccessor())); 4406 for (int i = 1; i <= 50; i++) { 4407 if (hasAnyExampleValues(sd, Integer.toString(i))) 4408 examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i)))); 4409 } 4410 } 4411 return examples; 4412 } 4413 4414 private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException { 4415 ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 4416 org.hl7.fhir.r5.elementmodel.Element r = new org.hl7.fhir.r5.elementmodel.Element(ed.getPath(), new Property(context, ed, profile)); 4417 SourcedChildDefinitions children = getChildMap(profile, ed, true); 4418 for (ElementDefinition child : children.getList()) { 4419 if (child.getPath().endsWith(".id")) { 4420 org.hl7.fhir.r5.elementmodel.Element id = new org.hl7.fhir.r5.elementmodel.Element("id", new Property(context, child, profile)); 4421 id.setValue(profile.getId()+accessor.getId()); 4422 r.getChildren().add(id); 4423 } else { 4424 org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); 4425 if (e != null) 4426 r.getChildren().add(e); 4427 } 4428 } 4429 return r; 4430 } 4431 4432 private org.hl7.fhir.r5.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException { 4433 DataType v = accessor.getExampleValue(ed); 4434 if (v != null) { 4435 return new ObjectConverter(context).convert(new Property(context, ed, profile), v); 4436 } else { 4437 org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile)); 4438 boolean hasValue = false; 4439 SourcedChildDefinitions children = getChildMap(profile, ed, true); 4440 for (ElementDefinition child : children.getList()) { 4441 if (!child.hasContentReference()) { 4442 org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); 4443 if (e != null) { 4444 hasValue = true; 4445 res.getChildren().add(e); 4446 } 4447 } 4448 } 4449 if (hasValue) 4450 return res; 4451 else 4452 return null; 4453 } 4454 } 4455 4456 private boolean hasAnyExampleValues(StructureDefinition sd, String index) { 4457 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4458 for (Extension ex : ed.getExtension()) { 4459 String ndx = ExtensionUtilities.readStringExtension(ex, "index"); 4460 Extension exv = ExtensionUtilities.getExtension(ex, "exValue"); 4461 if (exv != null) { 4462 DataType value = exv.getValue(); 4463 if (index.equals(ndx) && value != null) 4464 return true; 4465 } 4466 } 4467 return false; 4468 } 4469 4470 4471 private boolean hasAnyExampleValues(StructureDefinition sd) { 4472 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4473 if (ed.hasExample()) 4474 return true; 4475 return false; 4476 } 4477 4478 4479 public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException { 4480 sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy()); 4481 4482 if (sd.hasBaseDefinition()) { 4483 StructureDefinition base = findProfile(sd.getBaseDefinition(), sd); 4484 if (base == null) 4485 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE_DEFINITION_FOR_LOGICAL_MODEL__FROM_, sd.getBaseDefinition(), sd.getUrl())); 4486 copyElements(sd, base.getSnapshot().getElement()); 4487 } 4488 copyElements(sd, sd.getDifferential().getElement()); 4489 } 4490 4491 4492 private void copyElements(StructureDefinition sd, List<ElementDefinition> list) { 4493 for (ElementDefinition ed : list) { 4494 if (ed.getPath().contains(".")) { 4495 ElementDefinition n = ed.copy(); 4496 n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1)); 4497 sd.getSnapshot().addElement(n); 4498 } 4499 } 4500 } 4501 4502 4503 public void cleanUpDifferential(StructureDefinition sd) { 4504 if (sd.getDifferential().getElement().size() > 1) 4505 cleanUpDifferential(sd, 1); 4506 } 4507 4508 private void cleanUpDifferential(StructureDefinition sd, int start) { 4509 int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.'); 4510 int c = start; 4511 int len = sd.getDifferential().getElement().size(); 4512 HashSet<String> paths = new HashSet<String>(); 4513 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) { 4514 ElementDefinition ed = sd.getDifferential().getElement().get(c); 4515 if (!paths.contains(ed.getPath())) { 4516 paths.add(ed.getPath()); 4517 int ic = c+1; 4518 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4519 ic++; 4520 ElementDefinition slicer = null; 4521 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 4522 slices.add(ed); 4523 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) { 4524 ElementDefinition edi = sd.getDifferential().getElement().get(ic); 4525 if (ed.getPath().equals(edi.getPath())) { 4526 if (slicer == null) { 4527 slicer = new ElementDefinition(); 4528 slicer.setPath(edi.getPath()); 4529 slicer.getSlicing().setRules(SlicingRules.OPEN); 4530 sd.getDifferential().getElement().add(c, slicer); 4531 c++; 4532 ic++; 4533 } 4534 slices.add(edi); 4535 } 4536 ic++; 4537 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4538 ic++; 4539 } 4540 // now we're at the end, we're going to figure out the slicing discriminator 4541 if (slicer != null) 4542 determineSlicing(slicer, slices); 4543 } 4544 c++; 4545 if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) { 4546 cleanUpDifferential(sd, c); 4547 c++; 4548 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 4549 c++; 4550 } 4551 } 4552 } 4553 4554 4555 private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) { 4556 // first, name them 4557 int i = 0; 4558 for (ElementDefinition ed : slices) { 4559 if (ed.hasUserData(UserDataNames.SNAPSHOT_slice_name)) { 4560 ed.setSliceName(ed.getUserString(UserDataNames.SNAPSHOT_slice_name)); 4561 } else { 4562 i++; 4563 ed.setSliceName("slice-"+Integer.toString(i)); 4564 } 4565 } 4566 // now, the hard bit, how are they differentiated? 4567 // right now, we hard code this... 4568 if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension")) 4569 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 4570 else if (slicer.getPath().equals("DiagnosticReport.result")) 4571 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code"); 4572 else if (slicer.getPath().equals("Observation.related")) 4573 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code"); 4574 else if (slicer.getPath().equals("Bundle.entry")) 4575 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile"); 4576 else 4577 throw new Error("No slicing for "+slicer.getPath()); 4578 } 4579 4580 4581 public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) { 4582 if (discriminator.endsWith("@pattern")) 4583 return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 4584 if (discriminator.endsWith("@profile")) 4585 return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 4586 if (discriminator.endsWith("@type")) 4587 return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6)); 4588 if (discriminator.endsWith("@exists")) 4589 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 4590 if (isExists) 4591 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 4592 return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator); 4593 } 4594 4595 4596 private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) { 4597 return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str); 4598 } 4599 4600 4601 public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException { 4602 switch (t.getType()) { 4603 case PROFILE: return t.getPath()+"/@profile"; 4604 case PATTERN: return t.getPath()+"/@pattern"; 4605 case TYPE: return t.getPath()+"/@type"; 4606 case VALUE: return t.getPath(); 4607 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 4608 default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2"); 4609 } 4610 } 4611 4612 4613 public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) { 4614 String epath = url.substring(54); 4615 if (!epath.contains(".")) 4616 return null; 4617 String type = epath.substring(0, epath.indexOf(".")); 4618 StructureDefinition sd = context.fetchTypeDefinition(type); 4619 if (sd == null) 4620 return null; 4621 ElementDefinition ed = null; 4622 for (ElementDefinition t : sd.getSnapshot().getElement()) { 4623 if (t.getPath().equals(epath)) { 4624 ed = t; 4625 break; 4626 } 4627 } 4628 if (ed == null) 4629 return null; 4630 if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) { 4631 return null; 4632 } else { 4633 StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"); 4634 StructureDefinition ext = template.copy(); 4635 ext.setUrl(url); 4636 ext.setId("extension-"+epath); 4637 ext.setName("Extension-"+epath); 4638 ext.setTitle("Extension for r4 "+epath); 4639 ext.setStatus(sd.getStatus()); 4640 ext.setDate(sd.getDate()); 4641 ext.getContact().clear(); 4642 ext.getContact().addAll(sd.getContact()); 4643 ext.setFhirVersion(sd.getFhirVersion()); 4644 ext.setDescription(ed.getDefinition()); 4645 ext.getContext().clear(); 4646 ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf("."))); 4647 ext.getDifferential().getElement().clear(); 4648 ext.getSnapshot().getElement().get(3).setFixed(new UriType(url)); 4649 ext.getSnapshot().getElement().set(4, ed.copy()); 4650 ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary())); 4651 return ext; 4652 } 4653 4654 } 4655 4656 4657 public boolean isThrowException() { 4658 return wantThrowExceptions; 4659 } 4660 4661 4662 public void setThrowException(boolean exception) { 4663 this.wantThrowExceptions = exception; 4664 } 4665 4666 4667 public ValidationOptions getTerminologyServiceOptions() { 4668 return terminologyServiceOptions; 4669 } 4670 4671 4672 public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) { 4673 this.terminologyServiceOptions = terminologyServiceOptions; 4674 } 4675 4676 4677 public boolean isNewSlicingProcessing() { 4678 return newSlicingProcessing; 4679 } 4680 4681 4682 public ProfileUtilities setNewSlicingProcessing(boolean newSlicingProcessing) { 4683 this.newSlicingProcessing = newSlicingProcessing; 4684 return this; 4685 } 4686 4687 4688 public boolean isDebug() { 4689 return debug; 4690 } 4691 4692 4693 public void setDebug(boolean debug) { 4694 this.debug = debug; 4695 } 4696 4697 4698 public String getDefWebRoot() { 4699 return defWebRoot; 4700 } 4701 4702 4703 public void setDefWebRoot(String defWebRoot) { 4704 this.defWebRoot = defWebRoot; 4705 if (!this.defWebRoot.endsWith("/")) 4706 this.defWebRoot = this.defWebRoot + '/'; 4707 } 4708 4709 4710 public static StructureDefinition makeBaseDefinition(FHIRVersion fhirVersion) { 4711 return makeBaseDefinition(fhirVersion.toCode()); 4712 } 4713 public static StructureDefinition makeBaseDefinition(String fhirVersion) { 4714 StructureDefinition base = new StructureDefinition(); 4715 base.setId("Base"); 4716 base.setUrl("http://hl7.org/fhir/StructureDefinition/Base"); 4717 base.setVersion(fhirVersion); 4718 base.setName("Base"); 4719 base.setStatus(PublicationStatus.ACTIVE); 4720 base.setDate(new Date()); 4721 base.setFhirVersion(FHIRVersion.fromCode(fhirVersion)); 4722 base.setKind(StructureDefinitionKind.COMPLEXTYPE); 4723 base.setAbstract(true); 4724 base.setType("Base"); 4725 base.setWebPath("http://build.fhir.org/types.html#Base"); 4726 ElementDefinition e = base.getSnapshot().getElementFirstRep(); 4727 e.setId("Base"); 4728 e.setPath("Base"); 4729 e.setMin(0); 4730 e.setMax("*"); 4731 e.getBase().setPath("Base"); 4732 e.getBase().setMin(0); 4733 e.getBase().setMax("*"); 4734 e.setIsModifier(false); 4735 e = base.getDifferential().getElementFirstRep(); 4736 e.setId("Base"); 4737 e.setPath("Base"); 4738 e.setMin(0); 4739 e.setMax("*"); 4740 return base; 4741 } 4742 4743 public XVerExtensionManager getXver() { 4744 return xver; 4745 } 4746 4747 public ProfileUtilities setXver(XVerExtensionManager xver) { 4748 this.xver = xver; 4749 return this; 4750 } 4751 4752 4753 private List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) { 4754 List<ElementChoiceGroup> result = new ArrayList<>(); 4755 for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { 4756 ElementChoiceGroup grp = processConstraint(children, c); 4757 if (grp != null) { 4758 result.add(grp); 4759 } 4760 } 4761 return result; 4762 } 4763 4764 public ElementChoiceGroup processConstraint(List<ElementDefinition> children, ElementDefinitionConstraintComponent c) { 4765 if (!c.hasExpression()) { 4766 return null; 4767 } 4768 ExpressionNode expr = null; 4769 try { 4770 expr = fpe.parse(c.getExpression()); 4771 } catch (Exception e) { 4772 return null; 4773 } 4774 if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) { 4775 return null; 4776 } 4777 ExpressionNode n1 = expr.getGroup(); 4778 ExpressionNode n2 = expr.getOpNext(); 4779 if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) { 4780 return null; 4781 } 4782 ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals); 4783 while (n1 != null) { 4784 if (n1.getKind() != Kind.Name || n1.getInner() != null) { 4785 return null; 4786 } 4787 grp.elements.add(n1.getName()); 4788 if (n1.getOperation() == null || n1.getOperation() == Operation.Union) { 4789 n1 = n1.getOpNext(); 4790 } else { 4791 return null; 4792 } 4793 } 4794 int total = 0; 4795 for (String n : grp.elements) { 4796 boolean found = false; 4797 for (ElementDefinition child : children) { 4798 String name = tail(child.getPath()); 4799 if (n.equals(name)) { 4800 found = true; 4801 if (!"0".equals(child.getMax())) { 4802 total++; 4803 } 4804 } 4805 } 4806 if (!found) { 4807 return null; 4808 } 4809 } 4810 if (total <= 1) { 4811 return null; 4812 } 4813 return grp; 4814 } 4815 4816 public Set<String> getMasterSourceFileNames() { 4817 return masterSourceFileNames; 4818 } 4819 4820 public void setMasterSourceFileNames(Set<String> masterSourceFileNames) { 4821 this.masterSourceFileNames = masterSourceFileNames; 4822 } 4823 4824 4825 public Set<String> getLocalFileNames() { 4826 return localFileNames; 4827 } 4828 4829 public void setLocalFileNames(Set<String> localFileNames) { 4830 this.localFileNames = localFileNames; 4831 } 4832 4833 public ProfileKnowledgeProvider getPkp() { 4834 return pkp; 4835 } 4836 4837 4838 public static final String UD_ERROR_STATUS = "error-status"; 4839 public static final int STATUS_OK = 0; 4840 public static final int STATUS_HINT = 1; 4841 public static final int STATUS_WARNING = 2; 4842 public static final int STATUS_ERROR = 3; 4843 public static final int STATUS_FATAL = 4; 4844 private static final String ROW_COLOR_ERROR = "#ffcccc"; 4845 private static final String ROW_COLOR_FATAL = "#ff9999"; 4846 private static final String ROW_COLOR_WARNING = "#ffebcc"; 4847 private static final String ROW_COLOR_HINT = "#ebf5ff"; 4848 private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8"; 4849 4850 public String getRowColor(ElementDefinition element, boolean isConstraintMode) { 4851 switch (element.getUserInt(UD_ERROR_STATUS)) { 4852 case STATUS_HINT: return ROW_COLOR_HINT; 4853 case STATUS_WARNING: return ROW_COLOR_WARNING; 4854 case STATUS_ERROR: return ROW_COLOR_ERROR; 4855 case STATUS_FATAL: return ROW_COLOR_FATAL; 4856 } 4857 if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains(".")) 4858 return null; // ROW_COLOR_NOT_MUST_SUPPORT; 4859 else 4860 return null; 4861 } 4862 4863 public static boolean isExtensionDefinition(StructureDefinition sd) { 4864 return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension"); 4865 } 4866 4867 public AllowUnknownProfile getAllowUnknownProfile() { 4868 return allowUnknownProfile; 4869 } 4870 4871 public void setAllowUnknownProfile(AllowUnknownProfile allowUnknownProfile) { 4872 this.allowUnknownProfile = allowUnknownProfile; 4873 } 4874 4875 public static boolean isSimpleExtension(StructureDefinition sd) { 4876 if (!isExtensionDefinition(sd)) { 4877 return false; 4878 } 4879 ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); 4880 return value != null && !value.isProhibited(); 4881 } 4882 4883 public static boolean isComplexExtension(StructureDefinition sd) { 4884 if (!isExtensionDefinition(sd)) { 4885 return false; 4886 } 4887 ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); 4888 return value == null || value.isProhibited(); 4889 } 4890 4891 public static boolean isModifierExtension(StructureDefinition sd) { 4892 ElementDefinition defn = sd.getSnapshot().hasElement() ? sd.getSnapshot().getElementByPath("Extension") : sd.getDifferential().getElementByPath("Extension"); 4893 return defn != null && defn.getIsModifier(); 4894 } 4895 4896 public boolean isForPublication() { 4897 return forPublication; 4898 } 4899 4900 public void setForPublication(boolean forPublication) { 4901 this.forPublication = forPublication; 4902 } 4903 4904 public List<ValidationMessage> getMessages() { 4905 return messages; 4906 } 4907 4908 public static boolean isResourceBoundary(ElementDefinition ed) { 4909 return ed.getType().size() == 1 && "Resource".equals(ed.getTypeFirstRep().getCode()); 4910 } 4911 4912 public static boolean isSuppressIgnorableExceptions() { 4913 return suppressIgnorableExceptions; 4914 } 4915 4916 public static void setSuppressIgnorableExceptions(boolean suppressIgnorableExceptions) { 4917 ProfileUtilities.suppressIgnorableExceptions = suppressIgnorableExceptions; 4918 } 4919 4920 public void setMessages(List<ValidationMessage> messages) { 4921 if (messages != null) { 4922 this.messages = messages; 4923 wantThrowExceptions = false; 4924 } 4925 } 4926 4927 private Map<String, List<Property>> propertyCache = new HashMap<>(); 4928 4929 public Map<String, List<Property>> getCachedPropertyList() { 4930 return propertyCache; 4931 } 4932 4933 public void checkExtensions(ElementDefinition outcome) { 4934 outcome.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS)); 4935 if (outcome.hasBinding()) { 4936 outcome.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS)); 4937 } 4938 4939 } 4940 4941 public static void markExtensions(ElementDefinition ed, boolean overrideSource, StructureDefinition src) { 4942 for (Extension ex : ed.getExtension()) { 4943 markExtensionSource(ex, overrideSource, src); 4944 } 4945 for (Extension ex : ed.getBinding().getExtension()) { 4946 markExtensionSource(ex, overrideSource, src); 4947 } 4948 for (TypeRefComponent t : ed.getType()) { 4949 for (Extension ex : t.getExtension()) { 4950 markExtensionSource(ex, overrideSource, src); 4951 } 4952 } 4953 } 4954 4955 public static boolean hasObligations(StructureDefinition sd) { 4956 if (sd.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE)) { 4957 return true; 4958 } 4959 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 4960 if (ed.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE)) { 4961 return true; 4962 } 4963 for (TypeRefComponent tr : ed.getType()) { 4964 if (tr.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE)) { 4965 return true; 4966 } 4967 } 4968 } 4969 return false; 4970 } 4971 4972 public List<String> getSuppressedMappings() { 4973 return suppressedMappings; 4974 } 4975 4976 public void setSuppressedMappings(List<String> suppressedMappings) { 4977 this.suppressedMappings = suppressedMappings; 4978 } 4979 4980 public static String getCSUrl(StructureDefinition profile) { 4981 if (profile.hasExtension(ExtensionDefinitions.EXT_SD_CS_URL)) { 4982 return ExtensionUtilities.readStringExtension(profile, ExtensionDefinitions.EXT_SD_CS_URL); 4983 } else { 4984 return profile.getUrl()+"?codesystem"; 4985 } 4986 } 4987 4988 public static String getUrlFromCSUrl(String url) { 4989 if (url == null) { 4990 return null; 4991 } 4992 if (url.endsWith("?codesystem")) { 4993 return url.replace("?codesystem", ""); 4994 } else { 4995 return null; 4996 } 4997 } 4998 4999 public FHIRPathEngine getFpe() { 5000 return fpe; 5001 } 5002 5003}