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