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