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