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