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