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