001package org.hl7.fhir.r5.conformance; 002 003import java.io.BufferedReader; 004import java.io.FileNotFoundException; 005import java.io.FileReader; 006 007/* 008 Copyright (c) 2011+, HL7, Inc. 009 All rights reserved. 010 011 Redistribution and use in source and binary forms, with or without modification, 012 are permitted provided that the following conditions are met: 013 014 * Redistributions of source code must retain the above copyright notice, this 015 list of conditions and the following disclaimer. 016 * Redistributions in binary form must reproduce the above copyright notice, 017 this list of conditions and the following disclaimer in the documentation 018 and/or other materials provided with the distribution. 019 * Neither the name of HL7 nor the names of its contributors may be used to 020 endorse or promote products derived from this software without specific 021 prior written permission. 022 023 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 024 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 025 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 026 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 027 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 028 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 029 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 030 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 031 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 032 POSSIBILITY OF SUCH DAMAGE. 033 034 */ 035 036 037import java.io.IOException; 038import java.io.OutputStream; 039import java.text.DateFormat; 040import java.text.ParseException; 041import java.text.SimpleDateFormat; 042import java.util.ArrayList; 043import java.util.Collections; 044import java.util.Comparator; 045import java.util.Date; 046import java.util.HashMap; 047import java.util.HashSet; 048import java.util.Iterator; 049import java.util.List; 050import java.util.Map; 051import java.util.Set; 052 053import org.apache.commons.lang3.StringUtils; 054import org.hl7.fhir.exceptions.DefinitionException; 055import org.hl7.fhir.exceptions.FHIRException; 056import org.hl7.fhir.exceptions.FHIRFormatError; 057import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution; 058import org.hl7.fhir.r5.context.IWorkerContext; 059import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; 060import org.hl7.fhir.r5.elementmodel.ObjectConverter; 061import org.hl7.fhir.r5.elementmodel.Property; 062import org.hl7.fhir.r5.formats.IParser; 063import org.hl7.fhir.r5.model.Base; 064import org.hl7.fhir.r5.model.BooleanType; 065import org.hl7.fhir.r5.model.CanonicalType; 066import org.hl7.fhir.r5.model.CodeType; 067import org.hl7.fhir.r5.model.CodeableConcept; 068import org.hl7.fhir.r5.model.Coding; 069import org.hl7.fhir.r5.model.DataType; 070import org.hl7.fhir.r5.model.Element; 071import org.hl7.fhir.r5.model.ElementDefinition; 072import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode; 073import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; 074import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBaseComponent; 075import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 076import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; 077import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent; 078import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; 079import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; 080import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 081import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation; 082import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; 083import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 084import org.hl7.fhir.r5.model.Enumeration; 085import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 086import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; 087import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 088import org.hl7.fhir.r5.model.ExpressionNode; 089import org.hl7.fhir.r5.model.ExpressionNode.Kind; 090import org.hl7.fhir.r5.model.ExpressionNode.Operation; 091import org.hl7.fhir.r5.model.Extension; 092import org.hl7.fhir.r5.model.IdType; 093import org.hl7.fhir.r5.model.IntegerType; 094import org.hl7.fhir.r5.model.PrimitiveType; 095import org.hl7.fhir.r5.model.Quantity; 096import org.hl7.fhir.r5.model.Resource; 097import org.hl7.fhir.r5.model.StringType; 098import org.hl7.fhir.r5.model.StructureDefinition; 099import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType; 100import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent; 101import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent; 102import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 103import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent; 104import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; 105import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 106import org.hl7.fhir.r5.model.UriType; 107import org.hl7.fhir.r5.model.ValueSet; 108import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 109import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 110import org.hl7.fhir.r5.renderers.TerminologyRenderer; 111import org.hl7.fhir.r5.renderers.spreadsheets.SpreadsheetGenerator; 112import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 113import org.hl7.fhir.r5.utils.FHIRLexer; 114import org.hl7.fhir.r5.utils.FHIRPathEngine; 115import org.hl7.fhir.r5.utils.PublicationHacker; 116import org.hl7.fhir.r5.utils.ToolingExtensions; 117import org.hl7.fhir.r5.utils.TranslatingUtilities; 118import org.hl7.fhir.r5.utils.XVerExtensionManager; 119import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus; 120import org.hl7.fhir.r5.utils.formats.CSVWriter; 121import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 122import org.hl7.fhir.utilities.MarkDownProcessor; 123import org.hl7.fhir.utilities.Utilities; 124import org.hl7.fhir.utilities.VersionUtilities; 125import org.hl7.fhir.utilities.i18n.I18nConstants; 126import org.hl7.fhir.utilities.validation.ValidationMessage; 127import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 128import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 129import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 130import org.hl7.fhir.utilities.validation.ValidationOptions; 131import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 132import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 133import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 134import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 135import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 136import org.hl7.fhir.utilities.xhtml.XhtmlNode; 137import org.hl7.fhir.utilities.xml.SchematronWriter; 138import org.hl7.fhir.utilities.xml.SchematronWriter.Rule; 139import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType; 140import org.hl7.fhir.utilities.xml.SchematronWriter.Section; 141 142/** 143 * This class provides a set of utility operations for working with Profiles. 144 * Key functionality: 145 * * getChildMap --? 146 * * getChildList 147 * * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 148 * * closeDifferential: fill out a differential by excluding anything not mentioned 149 * * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions 150 * * generateTable: generate the HTML for a hierarchical table presentation of a structure 151 * * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point 152 * * summarize: describe the contents of a profile 153 * 154 * 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 155 * 156 * @author Grahame 157 * 158 */ 159public class ProfileUtilities extends TranslatingUtilities { 160 161 public class ElementDefinitionResolution { 162 163 private StructureDefinition source; 164 private ElementDefinition element; 165 166 public ElementDefinitionResolution(StructureDefinition source, ElementDefinition element) { 167 this.source = source; 168 this.element = element; 169 } 170 171 public StructureDefinition getSource() { 172 return source; 173 } 174 175 public ElementDefinition getElement() { 176 return element; 177 } 178 179 } 180 181 public class ElementRedirection { 182 183 private String path; 184 private ElementDefinition element; 185 186 public ElementRedirection(ElementDefinition element, String path) { 187 this.path = path; 188 this.element = element; 189 } 190 191 public ElementDefinition getElement() { 192 return element; 193 } 194 195 @Override 196 public String toString() { 197 return element.toString() + " : "+path; 198 } 199 200 public String getPath() { 201 return path; 202 } 203 204 } 205 206 public class TypeSlice { 207 private ElementDefinition defn; 208 private String type; 209 public TypeSlice(ElementDefinition defn, String type) { 210 super(); 211 this.defn = defn; 212 this.type = type; 213 } 214 public ElementDefinition getDefn() { 215 return defn; 216 } 217 public String getType() { 218 return type; 219 } 220 221 } 222 public class BaseTypeSlice { 223 private ElementDefinition defn; 224 private String type; 225 private int start; 226 private int end; 227 public boolean handled; 228 public BaseTypeSlice(ElementDefinition defn, String type, int start, int end) { 229 super(); 230 this.defn = defn; 231 this.type = type; 232 this.start = start; 233 this.end = end; 234 } 235 } 236 237 public static class ElementChoiceGroup { 238 private Row row; 239 private String name; 240 private boolean mandatory; 241 private List<String> elements = new ArrayList<>(); 242 243 public ElementChoiceGroup(String name, boolean mandatory) { 244 super(); 245 this.name = name; 246 this.mandatory = mandatory; 247 } 248 public Row getRow() { 249 return row; 250 } 251 public List<String> getElements() { 252 return elements; 253 } 254 public void setRow(Row row) { 255 this.row = row; 256 } 257 public String getName() { 258 return name; 259 } 260 } 261 262 private static final int MAX_RECURSION_LIMIT = 10; 263 264 public class ExtensionContext { 265 266 private ElementDefinition element; 267 private StructureDefinition defn; 268 269 public ExtensionContext(StructureDefinition ext, ElementDefinition ed) { 270 this.defn = ext; 271 this.element = ed; 272 } 273 274 public ElementDefinition getElement() { 275 return element; 276 } 277 278 public StructureDefinition getDefn() { 279 return defn; 280 } 281 282 public String getUrl() { 283 if (element == defn.getSnapshot().getElement().get(0)) 284 return defn.getUrl(); 285 else 286 return element.getSliceName(); 287 } 288 289 public ElementDefinition getExtensionValueDefinition() { 290 int i = defn.getSnapshot().getElement().indexOf(element)+1; 291 while (i < defn.getSnapshot().getElement().size()) { 292 ElementDefinition ed = defn.getSnapshot().getElement().get(i); 293 if (ed.getPath().equals(element.getPath())) 294 return null; 295 if (ed.getPath().startsWith(element.getPath()+".value") && !ed.hasSlicing()) 296 return ed; 297 i++; 298 } 299 return null; 300 } 301 } 302 303 private static final String ROW_COLOR_ERROR = "#ffcccc"; 304 private static final String ROW_COLOR_FATAL = "#ff9999"; 305 private static final String ROW_COLOR_WARNING = "#ffebcc"; 306 private static final String ROW_COLOR_HINT = "#ebf5ff"; 307 private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8"; 308 public static final int STATUS_OK = 0; 309 public static final int STATUS_HINT = 1; 310 public static final int STATUS_WARNING = 2; 311 public static final int STATUS_ERROR = 3; 312 public static final int STATUS_FATAL = 4; 313 314 315 private static final String DERIVATION_EQUALS = "derivation.equals"; 316 public static final String DERIVATION_POINTER = "derived.pointer"; 317 public static final String IS_DERIVED = "derived.fact"; 318 public static final String UD_ERROR_STATUS = "error-status"; 319 private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed"; 320 private static final boolean COPY_BINDING_EXTENSIONS = false; 321 private static final boolean DONT_DO_THIS = false; 322 private final boolean ADD_REFERENCE_TO_TABLE = true; 323 324 private boolean useTableForFixedValues = true; 325 private boolean debug; 326 327 // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here 328 private final IWorkerContext context; 329 private FHIRPathEngine fpe; 330 private List<ValidationMessage> messages; 331 private List<String> snapshotStack = new ArrayList<String>(); 332 private ProfileKnowledgeProvider pkp; 333 private boolean igmode; 334 private boolean exception; 335 private ValidationOptions terminologyServiceOptions = new ValidationOptions(); 336 private boolean newSlicingProcessing; 337 private String defWebRoot; 338 private boolean autoFixSliceNames; 339 private XVerExtensionManager xver; 340 private boolean wantFixDifferentialFirstElementType; 341 private Set<String> masterSourceFileNames; 342 343 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) { 344 super(); 345 this.context = context; 346 this.messages = messages; 347 this.pkp = pkp; 348 349 this.fpe = fpe; 350 if (context != null && this.fpe == null) { 351 this.fpe = new FHIRPathEngine(context, this); 352 } 353 } 354 355 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) { 356 super(); 357 this.context = context; 358 this.messages = messages; 359 this.pkp = pkp; 360 if (context != null) { 361 this.fpe = new FHIRPathEngine(context, this); 362 } 363 } 364 365 public static class UnusedTracker { 366 private boolean used; 367 } 368 369 public boolean isIgmode() { 370 return igmode; 371 } 372 373 public void setIgmode(boolean igmode) { 374 this.igmode = igmode; 375 } 376 377 public boolean isWantFixDifferentialFirstElementType() { 378 return wantFixDifferentialFirstElementType; 379 } 380 381 public void setWantFixDifferentialFirstElementType(boolean wantFixDifferentialFirstElementType) { 382 this.wantFixDifferentialFirstElementType = wantFixDifferentialFirstElementType; 383 } 384 385 public boolean isAutoFixSliceNames() { 386 return autoFixSliceNames; 387 } 388 389 public ProfileUtilities setAutoFixSliceNames(boolean autoFixSliceNames) { 390 this.autoFixSliceNames = autoFixSliceNames; 391 return this; 392 } 393 394 public interface ProfileKnowledgeProvider { 395 class BindingResolution { 396 public String display; 397 public String url; 398 } 399 boolean isDatatype(String typeSimple); 400 boolean isResource(String typeSimple); 401 boolean hasLinkFor(String typeSimple); 402 String getLinkFor(String corePath, String typeSimple); 403 BindingResolution resolveBinding(StructureDefinition def, 404 ElementDefinitionBindingComponent binding, String path) throws FHIRException; 405 BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException; 406 String getLinkForProfile(StructureDefinition profile, String url); 407 boolean prependLinks(); 408 String getLinkForUrl(String corePath, String s); 409 } 410 411 412 413 public List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException { 414 if (element.getContentReference() != null) { 415 List<ElementDefinition> list = null; 416 String id = null; 417 if (element.getContentReference().startsWith("#")) { 418 // internal reference 419 id = element.getContentReference().substring(1); 420 list = profile.getSnapshot().getElement(); 421 } else if (element.getContentReference().contains("#")) { 422 // external reference 423 String ref = element.getContentReference(); 424 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ref.substring(0, ref.indexOf("#"))); 425 if (sd == null) { 426 throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'"); 427 } 428 list = sd.getSnapshot().getElement(); 429 id = ref.substring(ref.indexOf("#")+1); 430 } else { 431 throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'"); 432 } 433 434 for (ElementDefinition e : list) { 435 if (id.equals(e.getId())) 436 return getChildMap(profile, e); 437 } 438 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_NAME_REFERENCE__AT_PATH_, element.getContentReference(), element.getPath())); 439 440 } else { 441 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 442 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 443 String path = element.getPath(); 444 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 445 ElementDefinition e = elements.get(index); 446 if (e.getPath().startsWith(path + ".")) { 447 // We only want direct children, not all descendants 448 if (!e.getPath().substring(path.length()+1).contains(".")) 449 res.add(e); 450 } else 451 break; 452 } 453 return res; 454 } 455 } 456 457 458 public List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException { 459 if (!element.hasSlicing()) 460 throw new Error(context.formatMessage(I18nConstants.GETSLICELIST_SHOULD_ONLY_BE_CALLED_WHEN_THE_ELEMENT_HAS_SLICING)); 461 462 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 463 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 464 String path = element.getPath(); 465 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 466 ElementDefinition e = elements.get(index); 467 if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) { 468 // We want elements with the same path (until we hit an element that doesn't start with the same path) 469 if (e.getPath().equals(element.getPath())) 470 res.add(e); 471 } else 472 break; 473 } 474 return res; 475 } 476 477 478 /** 479 * Given a Structure, navigate to the element given by the path and return the direct children of that element 480 * 481 * @param structure The structure to navigate into 482 * @param path The path of the element within the structure to get the children for 483 * @return A List containing the element children (all of them are Elements) 484 */ 485 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) { 486 return getChildList(profile, path, id, false); 487 } 488 489 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff) { 490 return getChildList(profile, path, id, diff, false); 491 } 492 493 public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff, boolean refs) { 494 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 495 496 boolean capturing = id==null; 497 if (id==null && !path.contains(".")) 498 capturing = true; 499 500 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 501 for (ElementDefinition e : list) { 502 if (e == null) 503 throw new Error(context.formatMessage(I18nConstants.ELEMENT__NULL_, profile.getUrl())); 504 if (e.getId() == null) 505 throw new Error(context.formatMessage(I18nConstants.ELEMENT_ID__NULL__ON_, e.toString(), profile.getUrl())); 506 507 if (!capturing && id!=null && e.getId().equals(id)) { 508 capturing = true; 509 } 510 511 // If our element is a slice, stop capturing children as soon as we see the next slice 512 if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path)) 513 break; 514 515 if (capturing) { 516 String p = e.getPath(); 517 518 if (refs && !Utilities.noString(e.getContentReference()) && path.startsWith(p)) { 519 if (path.length() > p.length()) { 520 return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff); 521 } else if (e.getContentReference().startsWith("#")) { 522 return getChildList(profile, e.getContentReference().substring(1), null, diff); 523 } else if (e.getContentReference().contains("#")) { 524 String url = e.getContentReference().substring(0, e.getContentReference().indexOf("#")); 525 StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); 526 if (sd == null) { 527 throw new DefinitionException("Unable to find Structure "+url); 528 } 529 return getChildList(sd, e.getContentReference().substring(e.getContentReference().indexOf("#")+1), null, diff); 530 } else { 531 return getChildList(profile, e.getContentReference(), null, diff); 532 } 533 534 } else if (p.startsWith(path+".") && !p.equals(path)) { 535 String tail = p.substring(path.length()+1); 536 if (!tail.contains(".")) { 537 res.add(e); 538 } 539 } 540 } 541 } 542 543 return res; 544 } 545 546 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff, boolean refs) { 547 return getChildList(structure, element.getPath(), element.getId(), diff, refs); 548 } 549 550 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) { 551 return getChildList(structure, element.getPath(), element.getId(), diff); 552 } 553 554 public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) { 555 return getChildList(structure, element.getPath(), element.getId(), false); 556 } 557 558 public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException { 559 if (base == null) 560 throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED)); 561 if (derived == null) 562 throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED)); 563 564 for (StructureDefinitionMappingComponent baseMap : base.getMapping()) { 565 boolean found = false; 566 for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) { 567 if (derivedMap.getUri() != null && derivedMap.getUri().equals(baseMap.getUri())) { 568 found = true; 569 break; 570 } 571 } 572 if (!found) { 573 derived.getMapping().add(baseMap); 574 } 575 } 576 } 577 578 /** 579 * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 580 * 581 * @param base - the base structure on which the differential will be applied 582 * @param differential - the differential to apply to the base 583 * @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) 584 * @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) 585 * @param trimDifferential - if this is true, then the snap short generator will remove any material in the element definitions that is not different to the base 586 * @return 587 * @throws FHIRException 588 * @throws DefinitionException 589 * @throws Exception 590 */ 591 public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException { 592 if (base == null) { 593 throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED)); 594 } 595 if (derived == null) { 596 throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED)); 597 } 598 checkNotGenerating(base, "Base for generating a snapshot for the profile "+derived.getUrl()); 599 checkNotGenerating(derived, "Focus for generating a snapshot"); 600 601 if (!base.hasType()) { 602 throw new DefinitionException(context.formatMessage(I18nConstants.BASE_PROFILE__HAS_NO_TYPE, base.getUrl())); 603 } 604 if (!derived.hasType()) { 605 throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_TYPE, derived.getUrl())); 606 } 607 if (!derived.hasDerivation()) { 608 throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_DERIVATION_VALUE_AND_SO_CANT_BE_PROCESSED, derived.getUrl())); 609 } 610 if (!base.getType().equals(derived.getType()) && derived.getDerivation() == TypeDerivationRule.CONSTRAINT) { 611 throw new DefinitionException(context.formatMessage(I18nConstants.BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___, base.getUrl(), base.getType(), derived.getUrl(), derived.getType())); 612 } 613 614 if (snapshotStack.contains(derived.getUrl())) { 615 throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString())); 616 } 617 derived.setUserData("profileutils.snapshot.generating", true); 618 snapshotStack.add(derived.getUrl()); 619 620 if (!Utilities.noString(webUrl) && !webUrl.endsWith("/")) 621 webUrl = webUrl + '/'; 622 623 if (defWebRoot == null) 624 defWebRoot = webUrl; 625 derived.setSnapshot(new StructureDefinitionSnapshotComponent()); 626 627 try { 628 checkDifferential(derived.getDifferential().getElement(), typeName(derived.getType()), derived.getUrl()); 629 checkDifferentialBaseType(derived); 630 631 // so we have two lists - the base list, and the differential list 632 // the differential list is only allowed to include things that are in the base list, but 633 // is allowed to include them multiple times - thereby slicing them 634 635 // our approach is to walk through the base list, and see whether the differential 636 // says anything about them. 637 int baseCursor = 0; 638 int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths 639 640 641 for (ElementDefinition e : derived.getDifferential().getElement()) 642 e.clearUserData(GENERATED_IN_SNAPSHOT); 643 644 // we actually delegate the work to a subroutine so we can re-enter it with a different cursors 645 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 646 StructureDefinitionSnapshotComponent baseSnapshot = base.getSnapshot(); 647 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 648 String derivedType = derived.getType(); 649 if (StructureDefinitionKind.LOGICAL.equals(derived.getKind()) && derived.getType().contains("/")) { 650 derivedType = derivedType.substring(derivedType.lastIndexOf("/")+1); 651 } 652 baseSnapshot = cloneSnapshot(baseSnapshot, base.getType(), derivedType); 653 } 654// if (derived.getId().equals("2.16.840.1.113883.10.20.22.2.1.1")) { 655// debug = true; 656// } 657 processPaths("", derived.getSnapshot(), baseSnapshot, diff, baseCursor, diffCursor, baseSnapshot.getElement().size()-1, 658 derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, webUrl, derived.present(), null, null, false, base.getUrl(), null, false, null, null, new ArrayList<ElementRedirection>(), base); 659 checkGroupConstraints(derived); 660 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 661 for (ElementDefinition e : diff.getElement()) { 662 if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) { 663 ElementDefinition outcome = updateURLs(url, webUrl, e.copy()); 664 e.setUserData(GENERATED_IN_SNAPSHOT, outcome); 665 derived.getSnapshot().addElement(outcome); 666 } 667 } 668 } 669 670 if (derived.getKind() != StructureDefinitionKind.LOGICAL && !derived.getSnapshot().getElementFirstRep().getType().isEmpty()) 671 throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl())); 672 updateMaps(base, derived); 673 674 setIds(derived, false); 675 if (debug) { 676 System.out.println("Differential: "); 677 for (ElementDefinition ed : derived.getDifferential().getElement()) 678 System.out.println(" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 679 System.out.println("Snapshot: "); 680 for (ElementDefinition ed : derived.getSnapshot().getElement()) 681 System.out.println(" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 682 } 683 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 684 //Check that all differential elements have a corresponding snapshot element 685 int ce = 0; 686 for (ElementDefinition e : diff.getElement()) { 687 if (!e.hasUserData("diff-source")) 688 throw new Error(context.formatMessage(I18nConstants.UNXPECTED_INTERNAL_CONDITION__NO_SOURCE_ON_DIFF_ELEMENT)); 689 else { 690 if (e.hasUserData(DERIVATION_EQUALS)) 691 ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_EQUALS, e.getUserData(DERIVATION_EQUALS)); 692 if (e.hasUserData(DERIVATION_POINTER)) 693 ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_POINTER, e.getUserData(DERIVATION_POINTER)); 694 } 695 if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) { 696 b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath()); 697 ce++; 698 if (e.hasId()) { 699 String msg = "No match found in the generated snapshot: check that the path and definitions are legal in the differential (including order)"; 700 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+e.getId(), msg, ValidationMessage.IssueSeverity.ERROR)); 701 } 702 } 703 } 704 if (!Utilities.noString(b.toString())) { 705 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)"; 706 System.out.println("Error in snapshot generation: "+msg); 707 if (!debug) { 708 System.out.println("Differential: "); 709 for (ElementDefinition ed : derived.getDifferential().getElement()) 710 System.out.println(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 711 System.out.println("Snapshot: "); 712 for (ElementDefinition ed : derived.getSnapshot().getElement()) 713 System.out.println(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); 714 } 715 if (exception) 716 throw new DefinitionException(msg); 717 else 718 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, msg, ValidationMessage.IssueSeverity.ERROR)); 719 } 720 // hack around a problem in R4 definitions (somewhere?) 721 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 722 for (ElementDefinitionMappingComponent mm : ed.getMapping()) { 723 if (mm.hasMap()) { 724 mm.setMap(mm.getMap().trim()); 725 } 726 } 727 for (ElementDefinitionConstraintComponent s : ed.getConstraint()) { 728 if (s.hasSource()) { 729 String ref = s.getSource(); 730 if (!Utilities.isAbsoluteUrl(ref)) { 731 if (ref.contains(".")) { 732 s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref.substring(0, ref.indexOf("."))+"#"+ref); 733 } else { 734 s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref); 735 } 736 } 737 } 738 } 739 } 740 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 741 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 742 if (!ed.hasBase()) { 743 ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax()); 744 } 745 } 746 } 747 // last, check for wrong profiles or target profiles 748 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 749 for (TypeRefComponent t : ed.getType()) { 750 for (UriType u : t.getProfile()) { 751 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue()); 752 if (sd == null) { 753 if (xver != null && xver.matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) { 754 sd = xver.makeDefinition(u.getValue()); 755 } 756 } 757 if (sd == null) { 758 if (messages != null) { 759 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING)); 760 } 761 } else { 762 String wt = t.getWorkingCode(); 763 if (ed.getPath().equals("Bundle.entry.response.outcome")) { 764 wt = "OperationOutcome"; 765 } 766 if (!sd.getType().equals(wt)) { 767 boolean ok = isCompatibleType(wt, sd); 768 if (!ok) { 769 String smsg = "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt; 770 if (exception) 771 throw new DefinitionException(smsg); 772 else 773 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), smsg, IssueSeverity.ERROR)); 774 } 775 } 776 } 777 } 778 } 779 } 780 } catch (Exception e) { 781 // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind 782 derived.setSnapshot(null); 783 derived.clearUserData("profileutils.snapshot.generating"); 784 throw e; 785 } 786 derived.clearUserData("profileutils.snapshot.generating"); 787 } 788 789 public void checkDifferentialBaseType(StructureDefinition derived) throws Error { 790 if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) { 791 if (wantFixDifferentialFirstElementType && typeMatchesAncestor(derived.getDifferential().getElementFirstRep().getType(), derived.getBaseDefinition())) { 792 derived.getDifferential().getElementFirstRep().getType().clear(); 793 } else if (derived.getKind() != StructureDefinitionKind.LOGICAL) { 794 throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_DIFFERENTIAL_ELEMENT)); 795 } 796 } 797 } 798 799 private boolean typeMatchesAncestor(List<TypeRefComponent> type, String baseDefinition) { 800 StructureDefinition sd = context.fetchResource(StructureDefinition.class, baseDefinition); 801 return sd != null && type.size() == 1 && sd.getType().equals(type.get(0).getCode()); 802 } 803 804 private String typeName(String type) { 805 if (Utilities.isAbsoluteUrl(type)) { 806 return type.substring(type.lastIndexOf("/")+1); 807 } else { 808 return type; 809 } 810 } 811 812 private void checkGroupConstraints(StructureDefinition derived) { 813 List<ElementDefinition> toRemove = new ArrayList<>(); 814// List<ElementDefinition> processed = new ArrayList<>(); 815 for (ElementDefinition element : derived.getSnapshot().getElement()) { 816 if (!toRemove.contains(element) && !element.hasSlicing() && !"0".equals(element.getMax())) { 817 checkForChildrenInGroup(derived, toRemove, element); 818 } 819 } 820 derived.getSnapshot().getElement().removeAll(toRemove); 821 } 822 823 public void checkForChildrenInGroup(StructureDefinition derived, List<ElementDefinition> toRemove, ElementDefinition element) throws Error { 824 List<ElementDefinition> children = getChildren(derived, element); 825 List<ElementChoiceGroup> groups = readChoices(element, children); 826 for (ElementChoiceGroup group : groups) { 827// System.out.println(children); 828 String mandated = null; 829 Set<String> names = new HashSet<>(); 830 for (ElementDefinition ed : children) { 831 String name = tail(ed.getPath()); 832 if (names.contains(name)) { 833 throw new Error("huh?"); 834 } else { 835 names.add(name); 836 } 837 if (group.getElements().contains(name)) { 838 if (ed.getMin() == 1) { 839 if (mandated == null) { 840 mandated = name; 841 } else { 842 throw new Error("Error: there are two mandatory elements in "+derived.getUrl()+" when there can only be one: "+mandated+" and "+name); 843 } 844 } 845 } 846 } 847 if (mandated != null) { 848 for (ElementDefinition ed : children) { 849 String name = tail(ed.getPath()); 850 if (group.getElements().contains(name) && !mandated.equals(name)) { 851 ed.setMax("0"); 852 addAllChildren(derived, ed, toRemove); 853 } 854 } 855 } 856 } 857 } 858 859 private List<ElementDefinition> getChildren(StructureDefinition derived, ElementDefinition element) { 860 List<ElementDefinition> elements = derived.getSnapshot().getElement(); 861 int index = elements.indexOf(element) + 1; 862 String path = element.getPath()+"."; 863 List<ElementDefinition> list = new ArrayList<>(); 864 while (index < elements.size()) { 865 ElementDefinition e = elements.get(index); 866 String p = e.getPath(); 867 if (p.startsWith(path) && !e.hasSliceName()) { 868 if (!p.substring(path.length()).contains(".")) { 869 list.add(e); 870 } 871 index++; 872 } else { 873 break; 874 } 875 } 876 return list; 877 } 878 879 private void addAllChildren(StructureDefinition derived, ElementDefinition element, List<ElementDefinition> toRemove) { 880 List<ElementDefinition> children = getChildList(derived, element); 881 for (ElementDefinition child : children) { 882 toRemove.add(child); 883 addAllChildren(derived, child, toRemove); 884 } 885 } 886 887 private void checkDifferential(List<ElementDefinition> elements, String type, String url) { 888 boolean first = true; 889 for (ElementDefinition ed : elements) { 890 if (!ed.hasPath()) { 891 throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url)); 892 } 893 String p = ed.getPath(); 894 if (p == null) { 895 throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_VALUE_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url)); 896 } 897 if (!((first && type.equals(p)) || p.startsWith(type+"."))) { 898 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__MUST_START_WITH_, p, url, type, (first ? " (or be '"+type+"')" : ""))); 899 } 900 if (p.contains(".")) { 901 // Element names (the parts of a path delineated by the '.' character) SHALL NOT contain whitespace (i.e. Unicode characters marked as whitespace) 902 // Element names SHALL NOT contain the characters ,:;'"/|?!@#$%^&*()[]{} 903 // Element names SHOULD not contain non-ASCII characters 904 // Element names SHALL NOT exceed 64 characters in length 905 String[] pl = p.split("\\."); 906 for (String pp : pl) { 907 if (pp.length() < 1) { 908 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_MISING_, p, url)); 909 } 910 if (pp.length() > 64) { 911 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_EXCEEDS_64_CHARS_IN_LENGTH, p, url)); 912 } 913 for (char ch : pp.toCharArray()) { 914 if (Character.isWhitespace(ch)) { 915 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NO_UNICODE_WHITESPACE, p, url)); 916 } 917 if (Utilities.existsInList(ch, ',', ':', ';', '\'', '"', '/', '|', '?', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '{', '}')) { 918 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch)); 919 } 920 if (ch < ' ' || ch > 'z') { 921 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch)); 922 } 923 } 924 if (pp.contains("[") || pp.contains("]")) { 925 if (!pp.endsWith("[x]") || (pp.substring(0, pp.length()-3).contains("[") || (pp.substring(0, pp.length()-3).contains("]")))) { 926 throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTERS_, p, url)); 927 } 928 } 929 } 930 } 931 } 932 } 933 934 935 private boolean isCompatibleType(String base, StructureDefinition sdt) { 936 StructureDefinition sdb = context.fetchTypeDefinition(base); 937 if (sdb.getType().equals(sdt.getType())) { 938 return true; 939 } 940 StructureDefinition sd = context.fetchTypeDefinition(sdt.getType()); 941 while (sd != null) { 942 if (sd.getType().equals(sdb.getType())) { 943 return true; 944 } 945 if (sd.getUrl().equals(sdb.getUrl())) { 946 return true; 947 } 948 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 949 } 950 return false; 951 } 952 953 954 private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) { 955 StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent(); 956 for (ElementDefinition sed : source.getElement()) { 957 ElementDefinition ted = sed.copy(); 958 diff.getElement().add(ted); 959 ted.setUserData("diff-source", sed); 960 } 961 return diff; 962 } 963 964 private StructureDefinitionSnapshotComponent cloneSnapshot(StructureDefinitionSnapshotComponent source, String baseType, String derivedType) { 965 StructureDefinitionSnapshotComponent diff = new StructureDefinitionSnapshotComponent(); 966 for (ElementDefinition sed : source.getElement()) { 967 ElementDefinition ted = sed.copy(); 968 ted.setId(ted.getId().replaceFirst(baseType,derivedType)); 969 ted.setPath(ted.getPath().replaceFirst(baseType,derivedType)); 970 diff.getElement().add(ted); 971 } 972 return diff; 973 } 974 975 private String constraintSummary(ElementDefinition ed) { 976 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 977 if (ed.hasPattern()) 978 b.append("pattern="+ed.getPattern().fhirType()); 979 if (ed.hasFixed()) 980 b.append("fixed="+ed.getFixed().fhirType()); 981 if (ed.hasConstraint()) 982 b.append("constraints="+ed.getConstraint().size()); 983 return b.toString(); 984 } 985 986 987 private String sliceSummary(ElementDefinition ed) { 988 if (!ed.hasSlicing() && !ed.hasSliceName()) 989 return ""; 990 if (ed.hasSliceName()) 991 return " (slicename = "+ed.getSliceName()+")"; 992 993 StringBuilder b = new StringBuilder(); 994 boolean first = true; 995 for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) { 996 if (first) 997 first = false; 998 else 999 b.append("|"); 1000 b.append(d.getPath()); 1001 } 1002 return " (slicing by "+b.toString()+")"; 1003 } 1004 1005 1006 private String typeSummary(ElementDefinition ed) { 1007 StringBuilder b = new StringBuilder(); 1008 boolean first = true; 1009 for (TypeRefComponent tr : ed.getType()) { 1010 if (first) 1011 first = false; 1012 else 1013 b.append("|"); 1014 b.append(tr.getWorkingCode()); 1015 } 1016 return b.toString(); 1017 } 1018 1019 private String typeSummaryWithProfile(ElementDefinition ed) { 1020 StringBuilder b = new StringBuilder(); 1021 boolean first = true; 1022 for (TypeRefComponent tr : ed.getType()) { 1023 if (first) 1024 first = false; 1025 else 1026 b.append("|"); 1027 b.append(tr.getWorkingCode()); 1028 if (tr.hasProfile()) { 1029 b.append("("); 1030 b.append(tr.getProfile()); 1031 b.append(")"); 1032 1033 } 1034 } 1035 return b.toString(); 1036 } 1037 1038 1039 private boolean findMatchingElement(String id, List<ElementDefinition> list) { 1040 for (ElementDefinition ed : list) { 1041 if (ed.getId().equals(id)) 1042 return true; 1043 if (id.endsWith("[x]")) { 1044 if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains(".")) 1045 return true; 1046 } 1047 } 1048 return false; 1049 } 1050 1051 1052 /** 1053 * @param trimDifferential 1054 * @param srcSD 1055 * @throws DefinitionException, FHIRException 1056 * @throws Exception 1057 */ 1058 private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit, 1059 int diffLimit, String url, String webUrl, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone, ElementDefinition slicer, String typeSlicingPath, List<ElementRedirection> redirector, StructureDefinition srcSD) throws DefinitionException, FHIRException { 1060 if (debug) { 1061 System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", k "+(redirector == null ? "null" : redirector.toString())+")"); 1062 } 1063 ElementDefinition res = null; 1064 List<TypeSlice> typeList = new ArrayList<>(); 1065 // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries) 1066 while (baseCursor <= baseLimit) { 1067 // get the current focus of the base, and decide what to do 1068 ElementDefinition currentBase = base.getElement().get(baseCursor); 1069 String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector); 1070 if (debug) { 1071 System.out.println(indent+" - "+cpath+": base = "+baseCursor+" ("+descED(base.getElement(),baseCursor)+") to "+baseLimit+" ("+descED(base.getElement(),baseLimit)+"), diff = "+diffCursor+" ("+descED(differential.getElement(),diffCursor)+") to "+diffLimit+" ("+descED(differential.getElement(),diffLimit)+") "+ 1072 "(slicingDone = "+slicingDone+") (diffpath= "+(differential.getElement().size() > diffCursor ? differential.getElement().get(diffCursor).getPath() : "n/a")+")"); 1073 } 1074 List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get a list of matching elements in scope 1075 1076 // in the simple case, source is not sliced. 1077 if (!currentBase.hasSlicing() || cpath.equals(typeSlicingPath)) { 1078 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 1079 // so we just copy it in 1080 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 1081 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1082 updateFromBase(outcome, currentBase); 1083 markDerived(outcome); 1084 if (resultPathBase == null) 1085 resultPathBase = outcome.getPath(); 1086 else if (!outcome.getPath().startsWith(resultPathBase)) 1087 throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH__OUTCOMEGETPATH___RESULTPATHBASE__, outcome.getPath(), resultPathBase)); 1088 result.getElement().add(outcome); 1089 if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), true)) { 1090 // well, the profile walks into this, so we need to as well 1091 // did we implicitly step into a new type? 1092 if (baseHasChildren(base, currentBase)) { // not a new type here 1093 processPaths(indent+" ", result, base, differential, baseCursor+1, diffCursor, baseLimit, diffLimit, url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); 1094 baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor+1, baseLimit); 1095 } else { 1096 if (outcome.getType().size() == 0 && !outcome.hasContentReference()) { 1097 throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, cpath, differential.getElement().get(diffCursor).getPath(), profileName)); 1098 } 1099 boolean nonExtension = false; 1100 if (outcome.getType().size() > 1) { 1101 for (TypeRefComponent t : outcome.getType()) { 1102 if (!t.getWorkingCode().equals("Reference")) { 1103 for (ElementDefinition ed : diffMatches) { 1104 if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension")) { 1105 nonExtension = true; 1106 } 1107 } 1108 } 1109 } 1110 } 1111 int start = diffCursor; 1112 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+".")) 1113 diffCursor++; 1114 if (nonExtension) { 1115 throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, cpath, differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName)); 1116 } 1117 if (outcome.hasContentReference()) { 1118 ElementDefinitionResolution tgt = getElementById(srcSD, base.getElement(), outcome.getContentReference()); 1119 if (tgt == null) 1120 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_REFERENCE_TO_, outcome.getContentReference())); 1121 replaceFromContentReference(outcome, tgt.getElement()); 1122 if (tgt.getSource() != srcSD) { 1123 base = tgt.getSource().getSnapshot(); 1124 int nbc = base.getElement().indexOf(tgt.getElement())+1; 1125 int nbl = nbc; 1126 while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+".")) 1127 nbl++; 1128 processPaths(indent+" ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getElement().getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), tgt.getSource()); 1129 } else { 1130 int nbc = base.getElement().indexOf(tgt.getElement())+1; 1131 int nbl = nbc; 1132 while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+".")) 1133 nbl++; 1134 System.out.println("Test!"); 1135 processPaths(indent+" ", result, base, differential, nbc, start, nbl-1, diffCursor-1, url, webUrl, profileName, tgt.getElement().getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), srcSD); 1136 } 1137 } else { 1138 StructureDefinition dt = outcome.getType().size() > 1 ? context.fetchTypeDefinition("Element") : getProfileForDataType(outcome.getType().get(0)); 1139 if (dt == null) { 1140 throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), cpath)); 1141 } 1142 contextName = dt.getUrl(); 1143 if (redirector.isEmpty()) { 1144 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, 1145 diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); 1146 } else { 1147 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, 1148 diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, currentBase, cpath), srcSD); 1149 } 1150 } 1151 } 1152 } 1153 baseCursor++; 1154 } else if (diffMatches.size() == 1 && (slicingDone || (!isImplicitSlicing(diffMatches.get(0), cpath) && !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName()))))) {// one matching element in the differential 1155 ElementDefinition template = null; 1156 if (diffMatches.get(0).hasType() && "Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode()) && !isValidType(diffMatches.get(0).getType().get(0), currentBase)) { 1157 throw new DefinitionException(context.formatMessage(I18nConstants.VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT, url, diffMatches.get(0).getPath(), diffMatches.get(0).getType().get(0), currentBase.typeSummary())); 1158 } 1159 if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 && diffMatches.get(0).getType().get(0).hasProfile() && !"Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode())) { 1160 CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0); 1161 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue()); 1162 if (sd == null && xver != null && xver.matchingUrl(p.getValue())) { 1163 switch (xver.status(p.getValue())) { 1164 case BadVersion: throw new FHIRException("Reference to invalid version in extension url "+p.getValue()); 1165 case Invalid: throw new FHIRException("Reference to invalid extension "+p.getValue()); 1166 case Unknown: throw new FHIRException("Reference to unknown extension "+p.getValue()); 1167 case Valid: 1168 sd = xver.makeDefinition(p.getValue()); 1169 generateSnapshot(context.fetchTypeDefinition("Extension"), sd, sd.getUrl(), webUrl, sd.getName()); 1170 } 1171 } 1172 if (sd != null) { 1173 if (!isMatchingType(sd, diffMatches.get(0).getType(), p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT))) { 1174 throw new DefinitionException(context.formatMessage(I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE2, sd.getUrl(), diffMatches.get(0).getPath(), sd.getType(), p.getValue(), diffMatches.get(0).getType().get(0).getWorkingCode())); 1175 } 1176 if (isGenerating(sd)) { 1177 // this is a special case, because we're only going to access the first element, and we can rely on the fact that it's already populated. 1178 // but we check anyway 1179 if (sd.getSnapshot().getElementFirstRep().isEmpty()) { 1180 throw new FHIRException(context.formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, sd.getUrl(), "Source for first element")); 1181 } 1182 } else if (!sd.hasSnapshot()) { 1183 StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 1184 if (sdb == null) 1185 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE__FOR_, sd.getBaseDefinition(), sd.getUrl())); 1186 checkNotGenerating(sdb, "an extension base"); 1187 generateSnapshot(sdb, sd, sd.getUrl(), (sdb.hasUserData("path")) ? Utilities.extractBaseUrl(sdb.getUserString("path")) : webUrl, sd.getName()); 1188 } 1189 ElementDefinition src; 1190 if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) { 1191 src = null; 1192 String eid = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT); 1193 for (ElementDefinition t : sd.getSnapshot().getElement()) { 1194 if (eid.equals(t.getId())) 1195 src = t; 1196 } 1197 if (src == null) 1198 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT__IN_, eid, p.getValue())); 1199 } else 1200 src = sd.getSnapshot().getElement().get(0); 1201 template = src.copy().setPath(currentBase.getPath()); 1202 template.setSliceName(null); 1203 // temporary work around 1204 if (!"Extension".equals(diffMatches.get(0).getType().get(0).getCode())) { 1205 template.setMin(currentBase.getMin()); 1206 template.setMax(currentBase.getMax()); 1207 } 1208 } 1209 } 1210 if (template == null) 1211 template = currentBase.copy(); 1212 else 1213 // some of what's in currentBase overrides template 1214 template = fillOutFromBase(template, currentBase); 1215 1216 ElementDefinition outcome = updateURLs(url, webUrl, template); 1217 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1218 if (res == null) 1219 res = outcome; 1220 updateFromBase(outcome, currentBase); 1221 if (diffMatches.get(0).hasSliceName()) { 1222 outcome.setSliceName(diffMatches.get(0).getSliceName()); 1223 if (!diffMatches.get(0).hasMin() && (diffMatches.size() > 1 || slicer == null || slicer.getSlicing().getRules() != SlicingRules.CLOSED) && !currentBase.hasSliceName()) { 1224 if (!cpath.endsWith("xtension.value[x]")) { // hack work around for problems with snapshots in official releases 1225 outcome.setMin(0); 1226 } 1227 } 1228 } 1229 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD); 1230 removeStatusExtensions(outcome); 1231// if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*") && !diffMatches.get(0).hasSlicing()) // if the base profile allows multiple types, but the profile only allows one, rename it 1232// outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode())); 1233 outcome.setSlicing(null); 1234 if (resultPathBase == null) 1235 resultPathBase = outcome.getPath(); 1236 else if (!outcome.getPath().startsWith(resultPathBase)) 1237 throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH)); 1238 result.getElement().add(outcome); 1239 baseCursor++; 1240 diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1; 1241 if (diffLimit >= diffCursor && outcome.getPath().contains(".") && (isDataType(outcome.getType()) || isBaseResource(outcome.getType()) || outcome.hasContentReference())) { // don't want to do this for the root, since that's base, and we're already processing it 1242 if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) { 1243 if (outcome.getType().size() > 1) { 1244 if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) { 1245 String en = tail(outcome.getPath()); 1246 String tn = tail(diffMatches.get(0).getPath()); 1247 String t = tn.substring(en.length()-3); 1248 if (isPrimitive(Utilities.uncapitalize(t))) 1249 t = Utilities.uncapitalize(t); 1250 List<TypeRefComponent> ntr = getByTypeName(outcome.getType(), t); // keep any additional information 1251 if (ntr.isEmpty()) 1252 ntr.add(new TypeRefComponent().setCode(t)); 1253 outcome.getType().clear(); 1254 outcome.getType().addAll(ntr); 1255 } 1256 if (outcome.getType().size() > 1) 1257 for (TypeRefComponent t : outcome.getType()) { 1258 if (!t.getCode().equals("Reference")) { 1259 boolean nonExtension = false; 1260 for (ElementDefinition ed : diffMatches) 1261 if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension")) 1262 nonExtension = true; 1263 if (nonExtension) 1264 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)); 1265 } 1266 } 1267 } 1268 int start = diffCursor; 1269 while (diffCursor <= diffLimit && differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) 1270 diffCursor++; 1271 if (outcome.hasContentReference()) { 1272 ElementDefinitionResolution tgt = getElementById(srcSD, base.getElement(), outcome.getContentReference()); 1273 if (tgt == null) 1274 throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_REFERENCE_TO_, outcome.getContentReference())); 1275 replaceFromContentReference(outcome, tgt.getElement()); 1276 if (tgt.getSource() != srcSD) { 1277 base = tgt.getSource().getSnapshot(); 1278 int nbc = base.getElement().indexOf(tgt.getElement())+1; 1279 int nbl = nbc; 1280 while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+".")) 1281 nbl++; 1282 processPaths(indent+" ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getElement().getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), tgt.getSource()); 1283 } else { 1284 int nbc = base.getElement().indexOf(tgt.getElement())+1; 1285 int nbl = nbc; 1286 while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+".")) 1287 nbl++; 1288 processPaths(indent+" ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getElement().getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), srcSD); 1289 } 1290 } else { 1291 StructureDefinition dt = outcome.getType().size() == 1 ? getProfileForDataType(outcome.getType().get(0)) : getProfileForDataType("Element"); 1292 if (dt == null) 1293 throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__FOR_TYPE__IN_PROFILE__BUT_CANT_FIND_TYPE, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName)); 1294 contextName = dt.getUrl(); 1295 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, 1296 diffCursor - 1, url, getWebUrl(dt, webUrl, indent), profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, new ArrayList<ElementRedirection>(), srcSD); 1297 } 1298 } 1299 } 1300 } else if (diffsConstrainTypes(diffMatches, cpath, typeList)) { 1301 int start = 0; 1302 int nbl = findEndOfElement(base, baseCursor); 1303 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 1304 ElementDefinition elementToRemove = null; 1305 boolean shortCut = !typeList.isEmpty() && typeList.get(0).type != null; 1306 // we come here whether they are sliced in the diff, or whether the short cut is used. 1307 if (shortCut) { 1308 // this is the short cut method, we've just dived in and specified a type slice. 1309 // in R3 (and unpatched R4, as a workaround right now... 1310 if (!FHIRVersion.isR4Plus(context.getVersion()) || !newSlicingProcessing) { // newSlicingProcessing is a work around for editorial loop dependency 1311 // we insert a cloned element with the right types at the start of the diffMatches 1312 ElementDefinition ed = new ElementDefinition(); 1313 ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath)); 1314 for (TypeSlice ts : typeList) 1315 ed.addType().setCode(ts.type); 1316 ed.setSlicing(new ElementDefinitionSlicingComponent()); 1317 ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 1318 ed.getSlicing().setRules(SlicingRules.CLOSED); 1319 ed.getSlicing().setOrdered(false); 1320 diffMatches.add(0, ed); 1321 differential.getElement().add(ndc, ed); 1322 elementToRemove = ed; 1323 } else { 1324 // as of R4, this changed; if there's no slice, there's no constraint on the slice types, only one the type. 1325 // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type. 1326 // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element 1327 ElementDefinition ed = new ElementDefinition(); 1328 ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath)); 1329 ed.setSlicing(new ElementDefinitionSlicingComponent()); 1330 ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 1331 ed.getSlicing().setRules(SlicingRules.CLOSED); 1332 ed.getSlicing().setOrdered(false); 1333 diffMatches.add(0, ed); 1334 differential.getElement().add(ndc, ed); 1335 elementToRemove = ed; 1336 } 1337 } 1338 int ndl = findEndOfElement(differential, ndc); 1339 // the first element is setting up the slicing 1340 1341 if (diffMatches.get(0).getSlicing().hasOrdered()) { 1342 if (diffMatches.get(0).getSlicing().getOrdered()) { 1343 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGORDERED__TRUE, cpath, url)); 1344 } 1345 } 1346 if (diffMatches.get(0).getSlicing().hasDiscriminator()) { 1347 if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1) { 1348 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORCOUNT__1, cpath, url)); 1349 } 1350 if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE) { 1351 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORTYPE__TYPE, cpath, url)); 1352 } 1353 if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath())) { 1354 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORPATH__THIS, cpath, url)); 1355 } 1356 } 1357 // check the slice names too while we're at it... 1358 for (TypeSlice ts : typeList) { 1359 if (ts.type != null) { 1360 String tn = rootName(cpath)+Utilities.capitalize(ts.type); 1361 if (!ts.defn.hasSliceName()) { 1362 ts.defn.setSliceName(tn); 1363 } else if (!ts.defn.getSliceName().equals(tn)) { 1364 if (autoFixSliceNames) { 1365 ts.defn.setSliceName(tn); 1366 } else { 1367 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_NAME_MUST_BE__BUT_IS_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.getSliceName())); 1368 } 1369 } if (!ts.defn.hasType()) { 1370 ts.defn.addType().setCode(ts.type); 1371 } else if (ts.defn.getType().size() > 1) { 1372 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_MORE_THAN_ONE_TYPE_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.typeSummary())); 1373 } else if (!ts.defn.getType().get(0).getCode().equals(ts.type)) { 1374 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_WRONG_TYPE_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.typeSummary())); 1375 } 1376 } 1377 } 1378 1379 // ok passed the checks. 1380 // copy the root diff, and then process any children it has 1381 ElementDefinition e = processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 1382 trimDifferential, contextName, resultPathBase, true, null, null, redirector, srcSD); 1383 if (e==null) 1384 throw new FHIRException(context.formatMessage(I18nConstants.DID_NOT_FIND_TYPE_ROOT_, diffMatches.get(0).getPath())); 1385 // now set up slicing on the e (cause it was wiped by what we called. 1386 e.setSlicing(new ElementDefinitionSlicingComponent()); 1387 e.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 1388 e.getSlicing().setRules(SlicingRules.CLOSED); // type slicing is always closed; the differential might call it open, but that just means it's not constraining the slices it doesn't mention 1389 e.getSlicing().setOrdered(false); 1390 1391 start++; 1392 1393 String fixedType = null; 1394 // now process the siblings, which should each be type constrained - and may also have their own children 1395 // now we process the base scope repeatedly for each instance of the item in the differential list 1396 for (int i = start; i < diffMatches.size(); i++) { 1397 // our processing scope for the differential is the item in the list, and all the items before the next one in the list 1398 if (diffMatches.get(i).getMin() > 0) { 1399 if (diffMatches.size() > i+1) { 1400 throw new FHIRException(context.formatMessage(I18nConstants.INVALID_SLICING__THERE_IS_MORE_THAN_ONE_TYPE_SLICE_AT__BUT_ONE_OF_THEM__HAS_MIN__1_SO_THE_OTHER_SLICES_CANNOT_EXIST, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName())); 1401 } else { 1402 e.setMin(1); 1403 } 1404 fixedType = determineFixedType(diffMatches, fixedType, i); 1405 } 1406 ndc = differential.getElement().indexOf(diffMatches.get(i)); 1407 ndl = findEndOfElement(differential, ndc); 1408 processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, e, null, redirector, srcSD); 1409 } 1410 if (elementToRemove != null) { 1411 differential.getElement().remove(elementToRemove); 1412 ndl--; 1413 } 1414 if (fixedType != null) { 1415 for (Iterator<TypeRefComponent> iter = e.getType().iterator(); iter.hasNext(); ) { 1416 TypeRefComponent tr = iter.next(); 1417 if (!tr.getCode().equals(fixedType)) { 1418 iter.remove(); 1419 } 1420 } 1421 } 1422 if (!"0".equals(e.getMax())) { 1423 // check that there's a slice for each allowed types 1424 Set<String> allowedTypes = getListOfTypes(e); 1425 for (TypeSlice t : typeList) { 1426 if (t.type != null) { 1427 allowedTypes.remove(t.type); 1428 } else if (t.getDefn().hasSliceName() && t.getDefn().getType().size() == 1) { 1429 allowedTypes.remove(t.getDefn().getType().get(0).getCode()); 1430 } 1431 } 1432 if (!allowedTypes.isEmpty()) { 1433 if (cpath.contains("xtension.value")) { 1434 for (Iterator<TypeRefComponent> iter = e.getType().iterator(); iter.hasNext(); ) { 1435 TypeRefComponent tr = iter.next(); 1436 if (allowedTypes.contains(tr.getCode())) { 1437 iter.remove(); 1438 } 1439 } 1440// System.out.println("!!: Extension Error at "+cpath+": Allowed Types not sliced = "+allowedTypes+". !Extension!!"); 1441// throw new Error("Extension Error at "+cpath+": Allowed Types not sliced = "+allowedTypes+". !Extension!!"); 1442 1443 } else { 1444 e.getSlicing().setRules(SlicingRules.OPEN); 1445 } 1446 } 1447 } 1448 // ok, done with that - next in the base list 1449 baseCursor = nbl+1; 1450 diffCursor = ndl+1; 1451 1452 } else { 1453 // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct 1454 if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0))) 1455 // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1 1456 // (but you might do that in order to split up constraints by type) 1457 throw new DefinitionException(context.formatMessage(I18nConstants.ATTEMPT_TO_A_SLICE_AN_ELEMENT_THAT_DOES_NOT_REPEAT__FROM__IN_, currentBase.getPath(), currentBase.getPath(), contextName, url, diffMatches.get(0).getId(), sliceNames(diffMatches))); 1458 if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error 1459 throw new DefinitionException(context.formatMessage(I18nConstants.DIFFERENTIAL_DOES_NOT_HAVE_A_SLICE__B_OF_____IN_PROFILE_, currentBase.getPath(), baseCursor, baseLimit, diffCursor, diffLimit, url, cpath)); 1460 1461 // well, if it passed those preconditions then we slice the dest. 1462 int start = 0; 1463 int nbl = findEndOfElement(base, baseCursor); 1464// if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) { 1465 ElementDefinition slicerElement; 1466 if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && (nbl > baseCursor || differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1)) { // there's a default set before the slices 1467 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 1468 int ndl = findEndOfElement(differential, ndc); 1469 ElementDefinition e = processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 1470 trimDifferential, contextName, resultPathBase, true, null, null, redirector, srcSD); 1471 if (e==null) 1472 throw new FHIRException(context.formatMessage(I18nConstants.DID_NOT_FIND_SINGLE_SLICE_, diffMatches.get(0).getPath())); 1473 e.setSlicing(diffMatches.get(0).getSlicing()); 1474 slicerElement = e; 1475 start++; 1476 } else { 1477 // we're just going to accept the differential slicing at face value 1478 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 1479 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1480 updateFromBase(outcome, currentBase); 1481 1482 if (!diffMatches.get(0).hasSlicing()) 1483 outcome.setSlicing(makeExtensionSlicing()); 1484 else 1485 outcome.setSlicing(diffMatches.get(0).getSlicing().copy()); 1486 if (!outcome.getPath().startsWith(resultPathBase)) 1487 throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH)); 1488 result.getElement().add(outcome); 1489 slicerElement = outcome; 1490 1491 // differential - if the first one in the list has a name, we'll process it. Else we'll treat it as the base definition of the slice. 1492 if (!diffMatches.get(0).hasSliceName()) { 1493 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD); 1494 removeStatusExtensions(outcome); 1495 if (!outcome.hasContentReference() && !outcome.hasType()) { 1496 throw new DefinitionException(context.formatMessage(I18nConstants.NOT_DONE_YET)); 1497 } 1498 if (hasInnerDiffMatches(differential, currentBase.getPath(), diffCursor, diffLimit, base.getElement(), false)) { 1499 if (baseHasChildren(base, currentBase)) { // not a new type here 1500 throw new Error("This situation is not yet handled (constrain slicing to 1..1 and fix base slice for inline structure - please report issue to grahame@fhir.org along with a test case that reproduces this error (@ "+cpath+" | "+currentBase.getPath()+")"); 1501 } else { 1502 StructureDefinition dt = getTypeForElement(differential, diffCursor, profileName, diffMatches, outcome); 1503 contextName = dt.getUrl(); 1504 diffCursor++; 1505 start = diffCursor; 1506 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+".")) 1507 diffCursor++; 1508 diffCursor--; 1509 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, 1510 diffCursor, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); 1511 } 1512 } 1513 start++; 1514 // result.getElement().remove(result.getElement().size()-1); 1515 } else 1516 checkExtensionDoco(outcome); 1517 } 1518 // now, for each entry in the diff matches, we're going to process the base item 1519 // our processing scope for base is all the children of the current path 1520 int ndc = diffCursor; 1521 int ndl = diffCursor; 1522 for (int i = start; i < diffMatches.size(); i++) { 1523 // our processing scope for the differential is the item in the list, and all the items before the next one in the list 1524 ndc = differential.getElement().indexOf(diffMatches.get(i)); 1525 ndl = findEndOfElement(differential, ndc); 1526/* if (skipSlicingElement && i == 0) { 1527 ndc = ndc + 1; 1528 if (ndc > ndl) 1529 continue; 1530 }*/ 1531 // now we process the base scope repeatedly for each instance of the item in the differential list 1532 processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, slicerElement, null, redirector, srcSD); 1533 } 1534 // ok, done with that - next in the base list 1535 baseCursor = nbl+1; 1536 diffCursor = ndl+1; 1537 } 1538 } else { 1539 // the item is already sliced in the base profile. 1540 // here's the rules 1541 // 1. irrespective of whether the slicing is ordered or not, the definition order must be maintained 1542 // 2. slice element names have to match. 1543 // 3. new slices must be introduced at the end 1544 // corallory: you can't re-slice existing slices. is that ok? 1545 1546 // we're going to need this: 1547 String path = currentBase.getPath(); 1548 ElementDefinition original = currentBase; 1549 1550 if (diffMatches.isEmpty()) { 1551 if (hasInnerDiffMatches(differential, path, diffCursor, diffLimit, base.getElement(), true)) { 1552 // so we just copy it in 1553 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 1554 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1555 updateFromBase(outcome, currentBase); 1556 markDerived(outcome); 1557 if (resultPathBase == null) 1558 resultPathBase = outcome.getPath(); 1559 else if (!outcome.getPath().startsWith(resultPathBase)) 1560 throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH)); 1561 result.getElement().add(outcome); 1562 // the profile walks into this, so we need to as well 1563 // did we implicitly step into a new type? 1564 if (baseHasChildren(base, currentBase)) { // not a new type here 1565 processPaths(indent+" ", result, base, differential, baseCursor+1, diffCursor, baseLimit, diffLimit, url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); 1566 baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor, baseLimit); 1567 } else { 1568 StructureDefinition dt = getTypeForElement(differential, diffCursor, profileName, diffMatches, outcome); 1569 contextName = dt.getUrl(); 1570 int start = diffCursor; 1571 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+".")) 1572 diffCursor++; 1573 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, 1574 diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); 1575 } 1576 baseCursor++; 1577 } else { 1578 // the differential doesn't say anything about this item 1579 // copy across the currentbase, and all of its children and siblings 1580 while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) { 1581 ElementDefinition outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy()); 1582 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1583 if (!outcome.getPath().startsWith(resultPathBase)) 1584 throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH_IN_PROFILE___VS_, profileName, outcome.getPath(), resultPathBase)); 1585 result.getElement().add(outcome); // so we just copy it in 1586 baseCursor++; 1587 } 1588 } 1589 } else if (diffsConstrainTypes(diffMatches, cpath, typeList)) { 1590 int start = 0; 1591 int nbl = findEndOfElement(base, baseCursor); 1592 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 1593 ElementDefinition elementToRemove = null; 1594 boolean shortCut = (!typeList.isEmpty() && typeList.get(0).type != null) || (diffMatches.get(0).hasSliceName() && !diffMatches.get(0).hasSlicing()); 1595 // we come here whether they are sliced in the diff, or whether the short cut is used. 1596 if (shortCut) { 1597 // this is the short cut method, we've just dived in and specified a type slice. 1598 // in R3 (and unpatched R4, as a workaround right now... 1599 if (!FHIRVersion.isR4Plus(context.getVersion()) || !newSlicingProcessing) { // newSlicingProcessing is a work around for editorial loop dependency 1600 // we insert a cloned element with the right types at the start of the diffMatches 1601 ElementDefinition ed = new ElementDefinition(); 1602 ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath)); 1603 for (TypeSlice ts : typeList) 1604 ed.addType().setCode(ts.type); 1605 ed.setSlicing(new ElementDefinitionSlicingComponent()); 1606 ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 1607 ed.getSlicing().setRules(SlicingRules.CLOSED); 1608 ed.getSlicing().setOrdered(false); 1609 diffMatches.add(0, ed); 1610 differential.getElement().add(ndc, ed); 1611 elementToRemove = ed; 1612 } else { 1613 // as of R4, this changed; if there's no slice, there's no constraint on the slice types, only one the type. 1614 // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type. 1615 // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element 1616 ElementDefinition ed = new ElementDefinition(); 1617 ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath)); 1618 ed.setSlicing(new ElementDefinitionSlicingComponent()); 1619 ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 1620 ed.getSlicing().setRules(SlicingRules.CLOSED); 1621 ed.getSlicing().setOrdered(false); 1622 diffMatches.add(0, ed); 1623 differential.getElement().add(ndc, ed); 1624 elementToRemove = ed; 1625 } 1626 } 1627 int ndl = findEndOfElement(differential, ndc); 1628 // the first element is setting up the slicing 1629 1630 if (diffMatches.get(0).getSlicing().hasOrdered()) { 1631 if (diffMatches.get(0).getSlicing().getOrdered()) { 1632 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGORDERED__TRUE, cpath, url)); 1633 } 1634 } 1635 if (diffMatches.get(0).getSlicing().hasDiscriminator()) { 1636 if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1) { 1637 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORCOUNT__1, cpath, url)); 1638 } 1639 if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE) { 1640 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORTYPE__TYPE, cpath, url)); 1641 } 1642 if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath())) { 1643 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORPATH__THIS, cpath, url)); 1644 } 1645 } 1646 // check the slice names too while we're at it... 1647 for (TypeSlice ts : typeList) { 1648 if (ts.type != null) { 1649 String tn = rootName(cpath)+Utilities.capitalize(ts.type); 1650 if (!ts.defn.hasSliceName()) { 1651 ts.defn.setSliceName(tn); 1652 } else if (!ts.defn.getSliceName().equals(tn)) { 1653 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_NAME_MUST_BE__BUT_IS_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.getSliceName())); 1654 } if (!ts.defn.hasType()) { 1655 ts.defn.addType().setCode(ts.type); 1656 } else if (ts.defn.getType().size() > 1) { 1657 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_MORE_THAN_ONE_TYPE_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.typeSummary())); 1658 } else if (!ts.defn.getType().get(0).getCode().equals(ts.type)) { 1659 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_WRONG_TYPE_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.typeSummary())); 1660 } 1661 } 1662 } 1663 1664 // ok passed the checks. 1665 // copy the root diff, and then process any children it has 1666 ElementDefinition e = processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 1667 trimDifferential, contextName, resultPathBase, true, null, cpath, redirector, srcSD); 1668 if (e==null) 1669 throw new FHIRException(context.formatMessage(I18nConstants.DID_NOT_FIND_TYPE_ROOT_, diffMatches.get(0).getPath())); 1670 // now set up slicing on the e (cause it was wiped by what we called. 1671 e.setSlicing(new ElementDefinitionSlicingComponent()); 1672 e.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 1673 e.getSlicing().setRules(SlicingRules.CLOSED); // type slicing is always closed; the differential might call it open, but that just means it's not constraining the slices it doesn't mention 1674 e.getSlicing().setOrdered(false); 1675 start++; 1676 1677 String fixedType = null; 1678 List<BaseTypeSlice> baseSlices = findBaseSlices(base, nbl); 1679 // now process the siblings, which should each be type constrained - and may also have their own children. they may match existing slices 1680 // now we process the base scope repeatedly for each instance of the item in the differential list 1681 for (int i = start; i < diffMatches.size(); i++) { 1682 String type = determineFixedType(diffMatches, fixedType, i); 1683 // our processing scope for the differential is the item in the list, and all the items before the next one in the list 1684 if (diffMatches.get(i).getMin() > 0) { 1685 if (diffMatches.size() > i+1) { 1686 throw new FHIRException(context.formatMessage(I18nConstants.INVALID_SLICING__THERE_IS_MORE_THAN_ONE_TYPE_SLICE_AT__BUT_ONE_OF_THEM__HAS_MIN__1_SO_THE_OTHER_SLICES_CANNOT_EXIST, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName())); 1687 } 1688 fixedType = type; 1689 } 1690 ndc = differential.getElement().indexOf(diffMatches.get(i)); 1691 ndl = findEndOfElement(differential, ndc); 1692 int sStart = baseCursor; 1693 int sEnd = nbl; 1694 BaseTypeSlice bs = chooseMatchingBaseSlice(baseSlices, type); 1695 if (bs != null) { 1696 sStart = bs.start; 1697 sEnd = bs.end; 1698 bs.handled = true; 1699 } 1700 processPaths(indent+" ", result, base, differential, sStart, ndc, sEnd, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, e, cpath, redirector, srcSD); 1701 } 1702 if (elementToRemove != null) { 1703 differential.getElement().remove(elementToRemove); 1704 ndl--; 1705 } 1706 if (fixedType != null) { 1707 for (Iterator<TypeRefComponent> iter = e.getType().iterator(); iter.hasNext(); ) { 1708 TypeRefComponent tr = iter.next(); 1709 if (!tr.getCode().equals(fixedType)) { 1710 iter.remove(); 1711 } 1712 } 1713 } 1714 for (BaseTypeSlice bs : baseSlices) { 1715 if (!bs.handled) { 1716 // ok we gimme up a fake differential that says nothing, and run that against the slice. 1717 StructureDefinitionDifferentialComponent fakeDiff = new StructureDefinitionDifferentialComponent(); 1718 fakeDiff.getElementFirstRep().setPath(bs.defn.getPath()); 1719 processPaths(indent+" ", result, base, fakeDiff, bs.start, 0, bs.end, 0, url, webUrl, profileName+tail(bs.defn.getPath()), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, e, cpath, redirector, srcSD); 1720 1721 } 1722 } 1723 // ok, done with that - next in the base list 1724 baseCursor = baseSlices.get(baseSlices.size()-1).end+1; 1725 diffCursor = ndl+1; 1726 //throw new Error("not done yet - slicing / types @ "+cpath); 1727 } else { 1728 // first - check that the slicing is ok 1729 boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED; 1730 int diffpos = 0; 1731 boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension"); 1732 if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing 1733// if (!isExtension) 1734// diffpos++; // if there's a slice on the first, we'll ignore any content it has 1735 ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing(); 1736 ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing(); 1737 if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement())) 1738 throw new DefinitionException(context.formatMessage(I18nConstants.SLICING_RULES_ON_DIFFERENTIAL__DO_NOT_MATCH_THOSE_ON_BASE___ORDER___, summarizeSlicing(dSlice), summarizeSlicing(bSlice), path, contextName)); 1739 if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator())) 1740 throw new DefinitionException(context.formatMessage(I18nConstants.SLICING_RULES_ON_DIFFERENTIAL__DO_NOT_MATCH_THOSE_ON_BASE___DISCIMINATOR___, summarizeSlicing(dSlice), summarizeSlicing(bSlice), path, contextName)); 1741 if (!currentBase.isChoice() && !ruleMatches(dSlice.getRules(), bSlice.getRules())) 1742 throw new DefinitionException(context.formatMessage(I18nConstants.SLICING_RULES_ON_DIFFERENTIAL__DO_NOT_MATCH_THOSE_ON_BASE___RULE___, summarizeSlicing(dSlice), summarizeSlicing(bSlice), path, contextName)); 1743 } 1744 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 1745 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1746 updateFromBase(outcome, currentBase); 1747 if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) { 1748 updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing()); 1749 updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url, srcSD); // if there's no slice, we don't want to update the unsliced description 1750 removeStatusExtensions(outcome); 1751 } else if (!diffMatches.get(0).hasSliceName()) { 1752 diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called 1753 } 1754 1755 result.getElement().add(outcome); 1756 1757 if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice 1758 diffpos++; 1759 } 1760 if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), false)) { 1761 int nbl = findEndOfElement(base, baseCursor); 1762 int ndx = differential.getElement().indexOf(diffMatches.get(0)); 1763 int ndc = ndx+(diffMatches.get(0).hasSlicing() ? 1 : 0); 1764 int ndl = findEndOfElement(differential, ndx); 1765 if (nbl == baseCursor) { 1766 if (base.getElement().get(baseCursor).getType().size() != 1) { 1767 throw new Error(context.formatMessage(I18nConstants.DIFFERENTIAL_WALKS_INTO____BUT_THE_BASE_DOES_NOT_AND_THERE_IS_NOT_A_SINGLE_FIXED_TYPE_THE_TYPE_IS__THIS_IS_NOT_HANDLED_YET, cpath, diffMatches.get(0).toString(), base.getElement().get(baseCursor).typeSummary())); 1768 } 1769 StructureDefinition dt = getProfileForDataType(base.getElement().get(baseCursor).getType().get(0)); 1770 if (dt == null) { 1771 throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath())); 1772 } 1773 contextName = dt.getUrl(); 1774 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+".")) 1775 diffCursor++; 1776 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1, ndc, dt.getSnapshot().getElement().size()-1, ndl, 1777 url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); 1778 } else { 1779 processPaths(indent+" ", result, base, differential, baseCursor+1, ndc, nbl, ndl, 1780 url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, null, null, srcSD); 1781 } 1782// throw new Error("Not done yet"); 1783// } else if (currentBase.getType().get(0).getCode().equals("BackboneElement") && diffMatches.size() > 0 && diffMatches.get(0).hasSliceName()) { 1784 } else if (currentBase.getType().get(0).getCode().equals("BackboneElement")) { 1785 // We need to copy children of the backbone element before we start messing around with slices 1786 int nbl = findEndOfElement(base, baseCursor); 1787 for (int i = baseCursor+1; i<=nbl; i++) { 1788 outcome = updateURLs(url, webUrl, base.getElement().get(i).copy()); 1789 result.getElement().add(outcome); 1790 } 1791 } 1792 1793 // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff. 1794 List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase); 1795 for (ElementDefinition baseItem : baseMatches) { 1796 baseCursor = base.getElement().indexOf(baseItem); 1797 outcome = updateURLs(url, webUrl, baseItem.copy()); 1798 updateFromBase(outcome, currentBase); 1799 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1800 outcome.setSlicing(null); 1801 if (!outcome.getPath().startsWith(resultPathBase)) 1802 throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH)); 1803 if (diffpos < diffMatches.size() && diffMatches.get(diffpos).hasSliceName() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) { 1804 // if there's a diff, we update the outcome with diff 1805 // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url); 1806 //then process any children 1807 int nbl = findEndOfElement(base, baseCursor); 1808 int ndc = differential.getElement().indexOf(diffMatches.get(diffpos)); 1809 int ndl = findEndOfElement(differential, ndc); 1810 // now we process the base scope repeatedly for each instance of the item in the differential list 1811 processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, resultPathBase, true, null, null, redirector, srcSD); 1812 // ok, done with that - now set the cursors for if this is the end 1813 baseCursor = nbl; 1814 diffCursor = ndl+1; 1815 diffpos++; 1816 } else { 1817 result.getElement().add(outcome); 1818 baseCursor++; 1819 // just copy any children on the base 1820 while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) { 1821 outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy()); 1822 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1823 if (!outcome.getPath().startsWith(resultPathBase)) 1824 throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH)); 1825 result.getElement().add(outcome); 1826 baseCursor++; 1827 } 1828 //Lloyd - add this for test T15 1829 baseCursor--; 1830 } 1831 } 1832 // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed 1833 boolean checkImplicitTypes = false; 1834 if (closed && diffpos < diffMatches.size()) { 1835 // this is a problem, unless we're on a polymorhpic type and we're going to constrain a slice that actually implicitly exists 1836 if (currentBase.getPath().endsWith("[x]")) { 1837 checkImplicitTypes = true; 1838 } else { 1839 throw new DefinitionException(context.formatMessage(I18nConstants.THE_BASE_SNAPSHOT_MARKS_A_SLICING_AS_CLOSED_BUT_THE_DIFFERENTIAL_TRIES_TO_EXTEND_IT_IN__AT__, profileName, path, cpath)); 1840 } 1841 } 1842 if (diffpos == diffMatches.size()) { 1843//Lloyd This was causing problems w/ Telus 1844// diffCursor++; 1845 } else { 1846 while (diffpos < diffMatches.size()) { 1847 ElementDefinition diffItem = diffMatches.get(diffpos); 1848 for (ElementDefinition baseItem : baseMatches) 1849 if (baseItem.getSliceName().equals(diffItem.getSliceName())) 1850 throw new DefinitionException(context.formatMessage(I18nConstants.NAMED_ITEMS_ARE_OUT_OF_ORDER_IN_THE_SLICE)); 1851 outcome = updateURLs(url, webUrl, currentBase.copy()); 1852 // outcome = updateURLs(url, diffItem.copy()); 1853 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1854 updateFromBase(outcome, currentBase); 1855 outcome.setSlicing(null); 1856 outcome.setMin(0); // we're in a slice, so it's only a mandatory if it's explicitly marked so 1857 if (!outcome.getPath().startsWith(resultPathBase)) 1858 throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH)); 1859 result.getElement().add(outcome); 1860 updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url, srcSD); 1861 removeStatusExtensions(outcome); 1862 // --- LM Added this 1863 diffCursor = differential.getElement().indexOf(diffItem)+1; 1864 if (!outcome.getType().isEmpty() && (/*outcome.getType().get(0).getCode().equals("Extension") || */differential.getElement().size() > diffCursor) && outcome.getPath().contains(".")/* && isDataType(outcome.getType())*/) { // don't want to do this for the root, since that's base, and we're already processing it 1865 if (!baseWalksInto(base.getElement(), baseCursor)) { 1866 if (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) { 1867 if (outcome.getType().size() > 1) 1868 for (TypeRefComponent t : outcome.getType()) { 1869 if (!t.getCode().equals("Reference")) 1870 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)); 1871 } 1872 TypeRefComponent t = outcome.getType().get(0); 1873 if (t.getCode().equals("BackboneElement")) { 1874 int baseStart = base.getElement().indexOf(currentBase)+1; 1875 int baseMax = baseStart + 1; 1876 while (baseMax < base.getElement().size() && base.getElement().get(baseMax).getPath().startsWith(currentBase.getPath()+".")) 1877 baseMax++; 1878 int start = diffCursor; 1879 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) 1880 diffCursor++; 1881 processPaths(indent+" ", result, base, differential, baseStart, start-1, baseMax-1, 1882 diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), base.getElement().get(0).getPath(), base.getElement().get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); 1883 1884 } else { 1885 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 1886 // if (t.getCode().equals("Extension") && t.hasProfile() && !t.getProfile().contains(":")) { 1887 // lloydfix dt = 1888 // } 1889 if (dt == null) 1890 throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__FOR_TYPE__IN_PROFILE__BUT_CANT_FIND_TYPE, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName)); 1891 contextName = dt.getUrl(); 1892 int start = diffCursor; 1893 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) 1894 diffCursor++; 1895 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1, 1896 diffCursor - 1, url, getWebUrl(dt, webUrl, indent), profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); 1897 } 1898 } 1899 } 1900 } 1901 // --- 1902 diffpos++; 1903 } 1904 } 1905 baseCursor++; 1906 } 1907 } 1908 } 1909 1910 int i = 0; 1911 for (ElementDefinition e : result.getElement()) { 1912 i++; 1913 if (e.hasMinElement() && e.getMinElement().getValue()==null) 1914 throw new Error(context.formatMessage(I18nConstants.NULL_MIN)); 1915 } 1916 return res; 1917 } 1918 1919 private Set<String> getListOfTypes(ElementDefinition e) { 1920 Set<String> result = new HashSet<>(); 1921 for (TypeRefComponent t : e.getType()) { 1922 result.add(t.getCode()); 1923 } 1924 return result; 1925 } 1926 1927 public StructureDefinition getTypeForElement(StructureDefinitionDifferentialComponent differential, int diffCursor, String profileName, 1928 List<ElementDefinition> diffMatches, ElementDefinition outcome) { 1929 if (outcome.getType().size() == 0) { 1930 throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), profileName)); 1931 } 1932 if (outcome.getType().size() > 1) { 1933 for (TypeRefComponent t : outcome.getType()) { 1934 if (!t.getWorkingCode().equals("Reference")) 1935 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)); 1936 } 1937 } 1938 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 1939 if (dt == null) 1940 throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath())); 1941 return dt; 1942 } 1943 1944 private String sliceNames(List<ElementDefinition> diffMatches) { 1945 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1946 for (ElementDefinition ed : diffMatches) { 1947 if (ed.hasSliceName()) { 1948 b.append(ed.getSliceName()); 1949 } 1950 } 1951 return b.toString(); 1952 } 1953 1954 private boolean isMatchingType(StructureDefinition sd, List<TypeRefComponent> types, String inner) { 1955 while (sd != null) { 1956 for (TypeRefComponent tr : types) { 1957 if (sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && sd.getType().equals(tr.getCode())) { 1958 return true; 1959 } 1960 if (inner == null && sd.getUrl().equals(tr.getCode())) { 1961 return true; 1962 } 1963 if (inner != null) { 1964 ElementDefinition ed = null; 1965 for (ElementDefinition t : sd.getSnapshot().getElement()) { 1966 if (inner.equals(t.getId())) { 1967 ed = t; 1968 } 1969 } 1970 if (ed != null) { 1971 return isMatchingType(ed.getType(), types); 1972 } 1973 } 1974 } 1975 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 1976 } 1977 return false; 1978 } 1979 1980 private boolean isMatchingType(List<TypeRefComponent> test, List<TypeRefComponent> desired) { 1981 for (TypeRefComponent t : test) { 1982 for (TypeRefComponent d : desired) { 1983 if (t.getCode().equals(d.getCode())) { 1984 return true; 1985 } 1986 } 1987 } 1988 return false; 1989 } 1990 1991 private boolean isValidType(TypeRefComponent t, ElementDefinition base) { 1992 for (TypeRefComponent tr : base.getType()) { 1993 if (tr.getCode().equals(t.getCode())) { 1994 return true; 1995 } 1996 if (tr.getWorkingCode().equals(t.getCode())) { 1997 System.out.println("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath()); 1998 return true; 1999 } 2000 } 2001 return false; 2002 } 2003 2004 private boolean isGenerating(StructureDefinition sd) { 2005 return sd.hasUserData("profileutils.snapshot.generating"); 2006 } 2007 2008 2009 private void checkNotGenerating(StructureDefinition sd, String role) { 2010 if (sd.hasUserData("profileutils.snapshot.generating")) { 2011 throw new FHIRException(context.formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, sd.getUrl(), role)); 2012 } 2013 } 2014 2015 private boolean isBaseResource(List<TypeRefComponent> types) { 2016 if (types.isEmpty()) 2017 return false; 2018 for (TypeRefComponent type : types) { 2019 String t = type.getWorkingCode(); 2020 if ("Resource".equals(t)) 2021 return false; 2022 } 2023 return true; 2024 2025 } 2026 2027 public String determineFixedType(List<ElementDefinition> diffMatches, String fixedType, int i) { 2028 if (diffMatches.get(i).getType().size() == 0 && diffMatches.get(i).hasSliceName()) { 2029 String n = tail(diffMatches.get(i).getPath()).replace("[x]", ""); 2030 String t = diffMatches.get(i).getSliceName().substring(n.length()); 2031 if (isDataType(t)) { 2032 fixedType = t; 2033 } else if (isPrimitive(Utilities.uncapitalize(t))) { 2034 fixedType = Utilities.uncapitalize(t); 2035 } else { 2036 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())); 2037 } 2038 } else if (diffMatches.get(i).getType().size() == 1) { 2039 fixedType = diffMatches.get(i).getType().get(0).getCode(); 2040 } else { 2041 throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__1_AT_, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName())); 2042 } 2043 return fixedType; 2044 } 2045 2046 2047 private BaseTypeSlice chooseMatchingBaseSlice(List<BaseTypeSlice> baseSlices, String type) { 2048 for (BaseTypeSlice bs : baseSlices) { 2049 if (bs.type.equals(type)) { 2050 return bs; 2051 } 2052 } 2053 return null; 2054 } 2055 2056 2057 private List<BaseTypeSlice> findBaseSlices(StructureDefinitionSnapshotComponent list, int start) { 2058 List<BaseTypeSlice> res = new ArrayList<>(); 2059 ElementDefinition base = list.getElement().get(start); 2060 int i = start + 1; 2061 while (i < list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) { 2062 i++; 2063 }; 2064 while (i < list.getElement().size() && list.getElement().get(i).getPath().equals(base.getPath()) && list.getElement().get(i).hasSliceName()) { 2065 int s = i; 2066 i++; 2067 while (i < list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) { 2068 i++; 2069 }; 2070 res.add(new BaseTypeSlice(list.getElement().get(s), list.getElement().get(s).getTypeFirstRep().getCode(), s, i-1)); 2071 } 2072 return res; 2073 } 2074 2075 2076 private String getWebUrl(StructureDefinition dt, String webUrl, String indent) { 2077 if (dt.hasUserData("path")) { 2078 // this is a hack, but it works for now, since we don't have deep folders 2079 String url = dt.getUserString("path"); 2080 int i = url.lastIndexOf("/"); 2081 if (i < 1) { 2082 return defWebRoot; 2083 } else { 2084 return url.substring(0, i+1); 2085 } 2086 } else { 2087 return webUrl; 2088 } 2089 } 2090 2091 private void removeStatusExtensions(ElementDefinition outcome) { 2092 outcome.removeExtension(ToolingExtensions.EXT_FMM_LEVEL); 2093 outcome.removeExtension(ToolingExtensions.EXT_FMM_SUPPORT); 2094 outcome.removeExtension(ToolingExtensions.EXT_FMM_DERIVED); 2095 outcome.removeExtension(ToolingExtensions.EXT_STANDARDS_STATUS); 2096 outcome.removeExtension(ToolingExtensions.EXT_NORMATIVE_VERSION); 2097 outcome.removeExtension(ToolingExtensions.EXT_WORKGROUP); 2098 outcome.removeExtension(ToolingExtensions.EXT_FMM_SUPPORT); 2099 outcome.removeExtension(ToolingExtensions.EXT_FMM_DERIVED); 2100 } 2101 2102 private String descED(List<ElementDefinition> list, int index) { 2103 return index >=0 && index < list.size() ? list.get(index).present() : "X"; 2104 } 2105 2106 private boolean baseHasChildren(StructureDefinitionSnapshotComponent base, ElementDefinition ed) { 2107 int index = base.getElement().indexOf(ed); 2108 if (index == -1 || index >= base.getElement().size()-1) 2109 return false; 2110 String p = base.getElement().get(index+1).getPath(); 2111 return isChildOf(p, ed.getPath()); 2112 } 2113 2114 2115 private boolean isChildOf(String sub, String focus) { 2116 if (focus.endsWith("[x]")) { 2117 focus = focus.substring(0, focus.length()-3); 2118 return sub.startsWith(focus); 2119 } else 2120 return sub.startsWith(focus+"."); 2121 } 2122 2123 2124 private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, int baseLimit) { 2125 return baseLimit+1; 2126 } 2127 2128 2129 private String rootName(String cpath) { 2130 String t = tail(cpath); 2131 return t.replace("[x]", ""); 2132 } 2133 2134 2135 private String determineTypeSlicePath(String path, String cpath) { 2136 String headP = path.substring(0, path.lastIndexOf(".")); 2137// String tailP = path.substring(path.lastIndexOf(".")+1); 2138 String tailC = cpath.substring(cpath.lastIndexOf(".")+1); 2139 return headP+"."+tailC; 2140 } 2141 2142 2143 private boolean isImplicitSlicing(ElementDefinition ed, String path) { 2144 if (ed == null || ed.getPath() == null || path == null) 2145 return false; 2146 if (path.equals(ed.getPath())) 2147 return false; 2148 boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3)); 2149 return ok; 2150 } 2151 2152 2153 private boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) { 2154// if (diffMatches.size() < 2) 2155 // return false; 2156 String p = diffMatches.get(0).getPath(); 2157 if (!p.endsWith("[x]") && !cPath.endsWith("[x]")) 2158 return false; 2159 typeList.clear(); 2160 String rn = tail(cPath); 2161 rn = rn.substring(0, rn.length()-3); 2162 for (int i = 0; i < diffMatches.size(); i++) { 2163 ElementDefinition ed = diffMatches.get(i); 2164 String n = tail(ed.getPath()); 2165 if (!n.startsWith(rn)) 2166 return false; 2167 String s = n.substring(rn.length()); 2168 if (!s.contains(".")) { 2169 if (ed.hasSliceName() && ed.getType().size() == 1) { 2170 typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode())); 2171 } else if (ed.hasSliceName() && ed.getType().size() == 0) { 2172 if (isDataType(s)) { 2173 typeList.add(new TypeSlice(ed, s)); 2174 } else if (isPrimitive(Utilities.uncapitalize(s))) { 2175 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 2176 } else { 2177 String tn = ed.getSliceName().substring(n.length()); 2178 if (isDataType(tn)) { 2179 typeList.add(new TypeSlice(ed, tn)); 2180 } else if (isPrimitive(Utilities.uncapitalize(tn))) { 2181 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(tn))); 2182 } 2183 } 2184 } else if (!ed.hasSliceName() && !s.equals("[x]")) { 2185 if (isDataType(s)) 2186 typeList.add(new TypeSlice(ed, s)); 2187 else if (isConstrainedDataType(s)) 2188 typeList.add(new TypeSlice(ed, baseType(s))); 2189 else if (isPrimitive(Utilities.uncapitalize(s))) 2190 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 2191 } else if (!ed.hasSliceName() && s.equals("[x]")) 2192 typeList.add(new TypeSlice(ed, null)); 2193 } 2194 } 2195 return true; 2196 } 2197 2198 2199 private List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, String path) { 2200 List<ElementRedirection> result = new ArrayList<ElementRedirection>(); 2201 result.addAll(redirector); 2202 result.add(new ElementRedirection(outcome, path)); 2203 return result; 2204 } 2205 2206 2207 private List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) { 2208 List<TypeRefComponent> res = new ArrayList<TypeRefComponent>(); 2209 for (TypeRefComponent tr : type) { 2210 if (t.equals(tr.getWorkingCode())) 2211 res.add(tr); 2212 } 2213 return res; 2214 } 2215 2216 2217 private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) { 2218 outcome.setContentReference(null); 2219 outcome.getType().clear(); // though it should be clear anyway 2220 outcome.getType().addAll(tgt.getType()); 2221 } 2222 2223 2224 private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) { 2225 if (cursor >= elements.size()) 2226 return false; 2227 String path = elements.get(cursor).getPath(); 2228 String prevPath = elements.get(cursor - 1).getPath(); 2229 return path.startsWith(prevPath + "."); 2230 } 2231 2232 2233 private ElementDefinition fillOutFromBase(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError { 2234 ElementDefinition res = profile.copy(); 2235 if (!res.hasSliceName()) 2236 res.setSliceName(usage.getSliceName()); 2237 if (!res.hasLabel()) 2238 res.setLabel(usage.getLabel()); 2239 for (Coding c : usage.getCode()) 2240 if (!res.hasCode(c)) 2241 res.addCode(c); 2242 2243 if (!res.hasDefinition()) 2244 res.setDefinition(usage.getDefinition()); 2245 if (!res.hasShort() && usage.hasShort()) 2246 res.setShort(usage.getShort()); 2247 if (!res.hasComment() && usage.hasComment()) 2248 res.setComment(usage.getComment()); 2249 if (!res.hasRequirements() && usage.hasRequirements()) 2250 res.setRequirements(usage.getRequirements()); 2251 for (StringType c : usage.getAlias()) 2252 if (!res.hasAlias(c.getValue())) 2253 res.addAlias(c.getValue()); 2254 if (!res.hasMin() && usage.hasMin()) 2255 res.setMin(usage.getMin()); 2256 if (!res.hasMax() && usage.hasMax()) 2257 res.setMax(usage.getMax()); 2258 2259 if (!res.hasFixed() && usage.hasFixed()) 2260 res.setFixed(usage.getFixed()); 2261 if (!res.hasPattern() && usage.hasPattern()) 2262 res.setPattern(usage.getPattern()); 2263 if (!res.hasExample() && usage.hasExample()) 2264 res.setExample(usage.getExample()); 2265 if (!res.hasMinValue() && usage.hasMinValue()) 2266 res.setMinValue(usage.getMinValue()); 2267 if (!res.hasMaxValue() && usage.hasMaxValue()) 2268 res.setMaxValue(usage.getMaxValue()); 2269 if (!res.hasMaxLength() && usage.hasMaxLength()) 2270 res.setMaxLength(usage.getMaxLength()); 2271 if (!res.hasMustSupport() && usage.hasMustSupport()) 2272 res.setMustSupport(usage.getMustSupport()); 2273 if (!res.hasBinding() && usage.hasBinding()) 2274 res.setBinding(usage.getBinding().copy()); 2275 for (ElementDefinitionConstraintComponent c : usage.getConstraint()) 2276 if (!res.hasConstraint(c.getKey())) 2277 res.addConstraint(c); 2278 for (Extension e : usage.getExtension()) { 2279 if (!res.hasExtension(e.getUrl())) 2280 res.addExtension(e.copy()); 2281 } 2282 2283 return res; 2284 } 2285 2286 2287 private boolean checkExtensionDoco(ElementDefinition base) { 2288 // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff 2289 boolean isExtension = (base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension")) && 2290 (!base.hasBase() || !"II.extension".equals(base.getBase().getPath())); 2291 if (isExtension) { 2292 base.setDefinition("An Extension"); 2293 base.setShort("Extension"); 2294 base.setCommentElement(null); 2295 base.setRequirementsElement(null); 2296 base.getAlias().clear(); 2297 base.getMapping().clear(); 2298 } 2299 return isExtension; 2300 } 2301 2302 2303 private String pathTail(List<ElementDefinition> diffMatches, int i) { 2304 2305 ElementDefinition d = diffMatches.get(i); 2306 String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath(); 2307 return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : ""); 2308 } 2309 2310 2311 private void markDerived(ElementDefinition outcome) { 2312 for (ElementDefinitionConstraintComponent inv : outcome.getConstraint()) 2313 inv.setUserData(IS_DERIVED, true); 2314 } 2315 2316 2317 public static String summarizeSlicing(ElementDefinitionSlicingComponent slice) { 2318 StringBuilder b = new StringBuilder(); 2319 boolean first = true; 2320 for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) { 2321 if (first) 2322 first = false; 2323 else 2324 b.append(", "); 2325 b.append(d.getType().toCode()+":"+d.getPath()); 2326 } 2327 b.append(" ("); 2328 if (slice.hasOrdered()) 2329 b.append(slice.getOrdered() ? "ordered" : "unordered"); 2330 b.append("/"); 2331 if (slice.hasRules()) 2332 b.append(slice.getRules().toCode()); 2333 b.append(")"); 2334 if (slice.hasDescription()) { 2335 b.append(" \""); 2336 b.append(slice.getDescription()); 2337 b.append("\""); 2338 } 2339 return b.toString(); 2340 } 2341 2342 2343 private void updateFromBase(ElementDefinition derived, ElementDefinition base) { 2344 if (base.hasBase()) { 2345 if (!derived.hasBase()) 2346 derived.setBase(new ElementDefinitionBaseComponent()); 2347 derived.getBase().setPath(base.getBase().getPath()); 2348 derived.getBase().setMin(base.getBase().getMin()); 2349 derived.getBase().setMax(base.getBase().getMax()); 2350 } else { 2351 if (!derived.hasBase()) 2352 derived.setBase(new ElementDefinitionBaseComponent()); 2353 derived.getBase().setPath(base.getPath()); 2354 derived.getBase().setMin(base.getMin()); 2355 derived.getBase().setMax(base.getMax()); 2356 } 2357 } 2358 2359 2360 private boolean pathStartsWith(String p1, String p2) { 2361 return p1.startsWith(p2) || (p2.endsWith("[x].") && p1.startsWith(p2.substring(0, p2.length()-4))); 2362 } 2363 2364 private boolean pathMatches(String p1, String p2) { 2365 return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains(".")); 2366 } 2367 2368 2369 private String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) { 2370 if (contextPath == null) 2371 return pathSimple; 2372// String ptail = pathSimple.substring(contextPath.length() + 1); 2373 if (redirector.size() > 0) { 2374 String ptail = null; 2375 if (contextPath.length() >= pathSimple.length()) { 2376 ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 2377 } else { 2378 ptail = pathSimple.substring(contextPath.length()+1); 2379 } 2380 return redirector.get(redirector.size()-1).getPath()+"."+ptail; 2381// return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1); 2382 } else { 2383 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 2384 return contextPath+"."+ptail; 2385 } 2386 } 2387 2388 private String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, String redirectSource) { 2389 String s; 2390 if (contextPath == null) 2391 s = pathSimple; 2392 else { 2393 if (redirector.size() > 0) { 2394 String ptail = null; 2395 if (redirectSource.length() >= pathSimple.length()) { 2396 ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 2397 } else { 2398 ptail = pathSimple.substring(redirectSource.length()+1); 2399 } 2400 // ptail = ptail.substring(ptail.indexOf(".")+1); 2401 s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail; 2402 } else { 2403 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 2404 s = contextPath+"."+ptail; 2405 } 2406 } 2407 return s; 2408 } 2409 2410 private StructureDefinition getProfileForDataType(TypeRefComponent type) { 2411 StructureDefinition sd = null; 2412 if (type.hasProfile()) { 2413 sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue()); 2414 if (sd == null) 2415 System.out.println("Failed to find referenced profile: " + type.getProfile()); 2416 } 2417 if (sd == null) 2418 sd = context.fetchTypeDefinition(type.getWorkingCode()); 2419 if (sd == null) 2420 System.out.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM 2421 return sd; 2422 } 2423 2424 private StructureDefinition getProfileForDataType(String type) { 2425 StructureDefinition sd = context.fetchTypeDefinition(type); 2426 if (sd == null) 2427 System.out.println("XX: failed to find profle for type: " + type); // debug GJM 2428 return sd; 2429 } 2430 2431 2432 public static String typeCode(List<TypeRefComponent> types) { 2433 StringBuilder b = new StringBuilder(); 2434 boolean first = true; 2435 for (TypeRefComponent type : types) { 2436 if (first) first = false; else b.append(", "); 2437 b.append(type.getWorkingCode()); 2438 if (type.hasTargetProfile()) 2439 b.append("{"+type.getTargetProfile()+"}"); 2440 else if (type.hasProfile()) 2441 b.append("{"+type.getProfile()+"}"); 2442 } 2443 return b.toString(); 2444 } 2445 2446 2447 private boolean isDataType(List<TypeRefComponent> types) { 2448 if (types.isEmpty()) 2449 return false; 2450 for (TypeRefComponent type : types) { 2451 String t = type.getWorkingCode(); 2452 if (!isDataType(t) && !isPrimitive(t)) 2453 return false; 2454 } 2455 return true; 2456 } 2457 2458 2459 /** 2460 * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url 2461 * @param url - the base url to use to turn internal references into absolute references 2462 * @param element - the Element to update 2463 * @return - the updated Element 2464 */ 2465 private ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) { 2466 if (element != null) { 2467 ElementDefinition defn = element; 2468 if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#")) 2469 defn.getBinding().setValueSet(url+defn.getBinding().getValueSet()); 2470 for (TypeRefComponent t : defn.getType()) { 2471 for (UriType u : t.getProfile()) { 2472 if (u.getValue().startsWith("#")) 2473 u.setValue(url+t.getProfile()); 2474 } 2475 for (UriType u : t.getTargetProfile()) { 2476 if (u.getValue().startsWith("#")) 2477 u.setValue(url+t.getTargetProfile()); 2478 } 2479 } 2480 if (webUrl != null) { 2481 // also, must touch up the markdown 2482 if (element.hasDefinition()) 2483 element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, baseSpecUrl(), context.getResourceNames(), masterSourceFileNames)); 2484 if (element.hasComment()) 2485 element.setComment(processRelativeUrls(element.getComment(), webUrl, baseSpecUrl(), context.getResourceNames(), masterSourceFileNames)); 2486 if (element.hasRequirements()) 2487 element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, baseSpecUrl(), context.getResourceNames(), masterSourceFileNames)); 2488 if (element.hasMeaningWhenMissing()) 2489 element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, baseSpecUrl(), context.getResourceNames(), masterSourceFileNames)); 2490 } 2491 } 2492 return element; 2493 } 2494 2495 public static String processRelativeUrls(String markdown, String webUrl, String basePath, List<String> resourceNames, Set<String> filenames) { 2496 StringBuilder b = new StringBuilder(); 2497 int i = 0; 2498 while (i < markdown.length()) { 2499 if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { 2500 int j = i + 2; 2501 while (j < markdown.length() && markdown.charAt(j) != ')') 2502 j++; 2503 if (j < markdown.length()) { 2504 String url = markdown.substring(i+2, j); 2505 if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) { 2506 // 2507 // In principle, relative URLs are supposed to be converted to absolute URLs in snapshots. 2508 // that's what this code is doing. 2509 // 2510 // But that hasn't always happened and there's packages out there where the snapshots 2511 // contain relative references that actually are references to the main specification 2512 // 2513 // This code is trying to guess which relative references are actually to the 2514 // base specification. 2515 // 2516 if (isLikelySourceURLReference(url, resourceNames, filenames)) { 2517 b.append("]("); 2518 b.append(basePath); 2519 i = i + 1; 2520 } else { 2521 b.append("]("); 2522 // disabled 7-Dec 2021 GDG - we don't want to fool with relative URLs at all? 2523 // b.append(webUrl); 2524 i = i + 1; 2525 } 2526 } else 2527 b.append(markdown.charAt(i)); 2528 } else 2529 b.append(markdown.charAt(i)); 2530 } else { 2531 b.append(markdown.charAt(i)); 2532 } 2533 i++; 2534 } 2535 return b.toString(); 2536 } 2537 2538 2539 public static boolean isLikelySourceURLReference(String url, List<String> resourceNames, Set<String> filenames) { 2540 if (resourceNames != null) { 2541 for (String n : resourceNames) { 2542 if (url.startsWith(n.toLowerCase()+".html")) { 2543 return true; 2544 } 2545 if (url.startsWith(n.toLowerCase()+"-definitions.html")) { 2546 return true; 2547 } 2548 } 2549 } 2550 if (filenames != null) { 2551 for (String n : filenames) { 2552 if (url.startsWith(n.toLowerCase())) { 2553 return true; 2554 } 2555 } 2556 } 2557 return 2558 url.startsWith("extensibility.html") || 2559 url.startsWith("terminologies.html") || 2560 url.startsWith("observation.html") || 2561 url.startsWith("codesystem.html") || 2562 url.startsWith("fhirpath.html") || 2563 url.startsWith("datatypes.html") || 2564 url.startsWith("operations.html") || 2565 url.startsWith("resource.html") || 2566 url.startsWith("elementdefinition.html") || 2567 url.startsWith("element-definitions.html") || 2568 url.startsWith("snomedct.html") || 2569 url.startsWith("loinc.html") || 2570 url.startsWith("http.html") || 2571 url.startsWith("references") || 2572 url.startsWith("narrative.html") || 2573 url.startsWith("search.html") || 2574 url.startsWith("patient-operation-match.html") || 2575 (url.startsWith("extension-") && url.contains(".html")) || 2576 url.startsWith("resource-definitions.html"); 2577 } 2578 2579 private String baseSpecUrl() { 2580 if (VersionUtilities.isR5Ver(context.getVersion())) { 2581 return "http://build.fhir.org/"; 2582 } 2583 if (VersionUtilities.isR4Ver(context.getVersion())) { 2584 return "http://hl7.org/fhir/R4/"; 2585 } 2586 if (VersionUtilities.isR3Ver(context.getVersion())) { 2587 return "http://hl7.org/fhir/STU3/"; 2588 } 2589 if (VersionUtilities.isR2BVer(context.getVersion())) { 2590 return "http://hl7.org/fhir/2016May/"; 2591 } 2592 if (VersionUtilities.isR2Ver(context.getVersion())) { 2593 return "http://hl7.org/fhir/DSTU2/"; 2594 } 2595 if (VersionUtilities.isR4BVer(context.getVersion())) { 2596 return "http://hl7.org/fhir/2021Mar/"; 2597 } 2598 return ""; 2599 } 2600 2601 private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) { 2602 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 2603 String path = current.getPath(); 2604 int cursor = list.indexOf(current)+1; 2605 while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) { 2606 if (pathMatches(list.get(cursor).getPath(), path)) 2607 result.add(list.get(cursor)); 2608 cursor++; 2609 } 2610 return result; 2611 } 2612 2613 private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) { 2614 if (src.hasOrderedElement()) 2615 dst.setOrderedElement(src.getOrderedElement().copy()); 2616 if (src.hasDiscriminator()) { 2617 // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll because it uses object equality, not string equality 2618 for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) { 2619 boolean found = false; 2620 for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) { 2621 if (matches(d, s)) { 2622 found = true; 2623 break; 2624 } 2625 } 2626 if (!found) 2627 dst.getDiscriminator().add(s); 2628 } 2629 } 2630 if (src.hasRulesElement()) 2631 dst.setRulesElement(src.getRulesElement().copy()); 2632 } 2633 2634 private boolean orderMatches(BooleanType diff, BooleanType base) { 2635 return (diff == null) || (base == null) || (diff.getValue() == base.getValue()); 2636 } 2637 2638 private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) { 2639 if (diff.isEmpty() || base.isEmpty()) 2640 return true; 2641 if (diff.size() != base.size()) 2642 return false; 2643 for (int i = 0; i < diff.size(); i++) 2644 if (!matches(diff.get(i), base.get(i))) 2645 return false; 2646 return true; 2647 } 2648 2649 private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) { 2650 return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath()); 2651 } 2652 2653 2654 private boolean ruleMatches(SlicingRules diff, SlicingRules base) { 2655 return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) || 2656 ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED)); 2657 } 2658 2659 private boolean isSlicedToOneOnly(ElementDefinition e) { 2660 return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); 2661 } 2662 2663 private ElementDefinitionSlicingComponent makeExtensionSlicing() { 2664 ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); 2665 slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); 2666 slice.setOrdered(false); 2667 slice.setRules(SlicingRules.OPEN); 2668 return slice; 2669 } 2670 2671 private boolean isExtension(ElementDefinition currentBase) { 2672 return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension"); 2673 } 2674 2675 private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException { 2676 end = Math.min(context.getElement().size(), end); 2677 start = Math.max(0, start); 2678 2679 for (int i = start; i <= end; i++) { 2680 ElementDefinition ed = context.getElement().get(i); 2681 String statedPath = ed.getPath(); 2682 if (!allowSlices && statedPath.equals(path) && ed.hasSliceName()) { 2683 return false; 2684 } else if (statedPath.startsWith(path+".")) { 2685 return true; 2686 } else if (path.endsWith("[x]") && statedPath.startsWith(path.substring(0, path.length() -3))) { 2687 return true; 2688 } else if (i != start && !allowSlices && !statedPath.startsWith(path+".")) { 2689 break; 2690 } else if (i != start && allowSlices && !statedPath.startsWith(path)) { 2691 break; 2692 } 2693 } 2694 return false; 2695 } 2696 2697 private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException { 2698 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 2699 String[] p = path.split("\\."); 2700 for (int i = start; i <= end; i++) { 2701 String statedPath = context.getElement().get(i).getPath(); 2702 String[] sp = statedPath.split("\\."); 2703 boolean ok = sp.length == p.length; 2704 for (int j = 0; j < p.length; j++) { 2705 ok = ok && sp.length > j && (p[j].equals(sp[j]) || isSameBase(p[j], sp[j])); 2706 } 2707// don't need this debug check - everything is ok 2708// if (ok != (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 && 2709// statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) && 2710// (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains("."))))) { 2711// System.out.println("mismatch in paths: "+statedPath +" vs " +path); 2712// } 2713 if (ok) { 2714 /* 2715 * Commenting this out because it raises warnings when profiling inherited elements. For example, 2716 * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry') 2717 * Not sure we have enough information here to do the check properly. Might be better done when we're sorting the profile? 2718 2719 if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath())) 2720 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)); 2721 2722 */ 2723 result.add(context.getElement().get(i)); 2724 } 2725 } 2726 return result; 2727 } 2728 2729 2730 public boolean isSameBase(String p, String sp) { 2731 return (p.endsWith("[x]") && sp.startsWith(p.substring(0, p.length()-3))) || (sp.endsWith("[x]") && p.startsWith(sp.substring(0, sp.length()-3))) ; 2732 } 2733 2734 private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) { 2735 int result = cursor; 2736 if (cursor >= context.getElement().size()) 2737 return result; 2738 String path = context.getElement().get(cursor).getPath()+"."; 2739 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 2740 result++; 2741 return result; 2742 } 2743 2744 private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) { 2745 int result = cursor; 2746 String path = context.getElement().get(cursor).getPath()+"."; 2747 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 2748 result++; 2749 return result; 2750 } 2751 2752 private boolean unbounded(ElementDefinition definition) { 2753 StringType max = definition.getMaxElement(); 2754 if (max == null) 2755 return false; // this is not valid 2756 if (max.getValue().equals("1")) 2757 return false; 2758 if (max.getValue().equals("0")) 2759 return false; 2760 return true; 2761 } 2762 2763 private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD) throws DefinitionException, FHIRException { 2764 source.setUserData(GENERATED_IN_SNAPSHOT, dest); 2765 // we start with a clone of the base profile ('dest') and we copy from the profile ('source') 2766 // over the top for anything the source has 2767 ElementDefinition base = dest; 2768 ElementDefinition derived = source; 2769 derived.setUserData(DERIVATION_POINTER, base); 2770 boolean isExtension = checkExtensionDoco(base); 2771 2772 2773 // Before applying changes, apply them to what's in the profile 2774 StructureDefinition profile = null; 2775 if (base.hasSliceName()) 2776 profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue()) : null; 2777 if (profile==null) 2778 profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue()) : null; 2779 if (profile != null) { 2780 ElementDefinition e = profile.getSnapshot().getElement().get(0); 2781 base.setDefinition(e.getDefinition()); 2782 base.setShort(e.getShort()); 2783 if (e.hasCommentElement()) 2784 base.setCommentElement(e.getCommentElement()); 2785 if (e.hasRequirementsElement()) 2786 base.setRequirementsElement(e.getRequirementsElement()); 2787 base.getAlias().clear(); 2788 base.getAlias().addAll(e.getAlias()); 2789 base.getMapping().clear(); 2790 base.getMapping().addAll(e.getMapping()); 2791 } 2792 if (derived != null) { 2793 if (derived.hasSliceName()) { 2794 base.setSliceName(derived.getSliceName()); 2795 } 2796 2797 if (derived.hasShortElement()) { 2798 if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false)) 2799 base.setShortElement(derived.getShortElement().copy()); 2800 else if (trimDifferential) 2801 derived.setShortElement(null); 2802 else if (derived.hasShortElement()) 2803 derived.getShortElement().setUserData(DERIVATION_EQUALS, true); 2804 } 2805 2806 if (derived.hasDefinitionElement()) { 2807 if (derived.getDefinition().startsWith("...")) 2808 base.setDefinition(base.getDefinition()+"\r\n"+derived.getDefinition().substring(3)); 2809 else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) 2810 base.setDefinitionElement(derived.getDefinitionElement().copy()); 2811 else if (trimDifferential) 2812 derived.setDefinitionElement(null); 2813 else if (derived.hasDefinitionElement()) 2814 derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true); 2815 } 2816 2817 if (derived.hasCommentElement()) { 2818 if (derived.getComment().startsWith("...")) 2819 base.setComment(base.getComment()+"\r\n"+derived.getComment().substring(3)); 2820 else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false)) 2821 base.setCommentElement(derived.getCommentElement().copy()); 2822 else if (trimDifferential) 2823 base.setCommentElement(derived.getCommentElement().copy()); 2824 else if (derived.hasCommentElement()) 2825 derived.getCommentElement().setUserData(DERIVATION_EQUALS, true); 2826 } 2827 2828 if (derived.hasLabelElement()) { 2829 if (derived.getLabel().startsWith("...")) 2830 base.setLabel(base.getLabel()+"\r\n"+derived.getLabel().substring(3)); 2831 else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false)) 2832 base.setLabelElement(derived.getLabelElement().copy()); 2833 else if (trimDifferential) 2834 base.setLabelElement(derived.getLabelElement().copy()); 2835 else if (derived.hasLabelElement()) 2836 derived.getLabelElement().setUserData(DERIVATION_EQUALS, true); 2837 } 2838 2839 if (derived.hasRequirementsElement()) { 2840 if (derived.getRequirements().startsWith("...")) 2841 base.setRequirements(base.getRequirements()+"\r\n"+derived.getRequirements().substring(3)); 2842 else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false)) 2843 base.setRequirementsElement(derived.getRequirementsElement().copy()); 2844 else if (trimDifferential) 2845 base.setRequirementsElement(derived.getRequirementsElement().copy()); 2846 else if (derived.hasRequirementsElement()) 2847 derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true); 2848 } 2849 // sdf-9 2850 if (derived.hasRequirements() && !base.getPath().contains(".")) 2851 derived.setRequirements(null); 2852 if (base.hasRequirements() && !base.getPath().contains(".")) 2853 base.setRequirements(null); 2854 2855 if (derived.hasAlias()) { 2856 if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false)) 2857 for (StringType s : derived.getAlias()) { 2858 if (!base.hasAlias(s.getValue())) 2859 base.getAlias().add(s.copy()); 2860 } 2861 else if (trimDifferential) 2862 derived.getAlias().clear(); 2863 else 2864 for (StringType t : derived.getAlias()) 2865 t.setUserData(DERIVATION_EQUALS, true); 2866 } 2867 2868 if (derived.hasMinElement()) { 2869 if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { 2870 if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply 2871 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 base min ("+Integer.toString(base.getMin())+")", ValidationMessage.IssueSeverity.ERROR)); 2872 base.setMinElement(derived.getMinElement().copy()); 2873 } else if (trimDifferential) 2874 derived.setMinElement(null); 2875 else 2876 derived.getMinElement().setUserData(DERIVATION_EQUALS, true); 2877 } 2878 2879 if (derived.hasMaxElement()) { 2880 if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { 2881 if (isLargerMax(derived.getMax(), base.getMax())) 2882 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR)); 2883 base.setMaxElement(derived.getMaxElement().copy()); 2884 } else if (trimDifferential) 2885 derived.setMaxElement(null); 2886 else 2887 derived.getMaxElement().setUserData(DERIVATION_EQUALS, true); 2888 } 2889 2890 if (derived.hasFixed()) { 2891 if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) { 2892 base.setFixed(derived.getFixed().copy()); 2893 } else if (trimDifferential) 2894 derived.setFixed(null); 2895 else 2896 derived.getFixed().setUserData(DERIVATION_EQUALS, true); 2897 } 2898 2899 if (derived.hasPattern()) { 2900 if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) { 2901 base.setPattern(derived.getPattern().copy()); 2902 } else 2903 if (trimDifferential) 2904 derived.setPattern(null); 2905 else 2906 derived.getPattern().setUserData(DERIVATION_EQUALS, true); 2907 } 2908 2909 for (ElementDefinitionExampleComponent ex : derived.getExample()) { 2910 boolean found = false; 2911 for (ElementDefinitionExampleComponent exS : base.getExample()) 2912 if (Base.compareDeep(ex, exS, false)) 2913 found = true; 2914 if (!found) 2915 base.addExample(ex.copy()); 2916 else if (trimDifferential) 2917 derived.getExample().remove(ex); 2918 else 2919 ex.setUserData(DERIVATION_EQUALS, true); 2920 } 2921 2922 if (derived.hasMaxLengthElement()) { 2923 if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false)) 2924 base.setMaxLengthElement(derived.getMaxLengthElement().copy()); 2925 else if (trimDifferential) 2926 derived.setMaxLengthElement(null); 2927 else 2928 derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true); 2929 } 2930 2931 if (derived.hasMaxValue()) { 2932 if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false)) 2933 base.setMaxValue(derived.getMaxValue().copy()); 2934 else if (trimDifferential) 2935 derived.setMaxValue(null); 2936 else 2937 derived.getMaxValue().setUserData(DERIVATION_EQUALS, true); 2938 } 2939 2940 if (derived.hasMinValue()) { 2941 if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false)) 2942 base.setMinValue(derived.getMinValue().copy()); 2943 else if (trimDifferential) 2944 derived.setMinValue(null); 2945 else 2946 derived.getMinValue().setUserData(DERIVATION_EQUALS, true); 2947 } 2948 2949 // todo: what to do about conditions? 2950 // condition : id 0..* 2951 2952 if (derived.hasMustSupportElement()) { 2953 if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))) { 2954 if (base.hasMustSupport() && base.getMustSupport() && !derived.getMustSupport()) { 2955 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)); 2956 } 2957 base.setMustSupportElement(derived.getMustSupportElement().copy()); 2958 } else if (trimDifferential) 2959 derived.setMustSupportElement(null); 2960 else 2961 derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true); 2962 } 2963 2964 2965 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 2966 // but extensions can change isModifier 2967 if (isExtension) { 2968 if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) 2969 base.setIsModifierElement(derived.getIsModifierElement().copy()); 2970 else if (trimDifferential) 2971 derived.setIsModifierElement(null); 2972 else if (derived.hasIsModifierElement()) 2973 derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true); 2974 if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) 2975 base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy()); 2976 else if (trimDifferential) 2977 derived.setIsModifierReasonElement(null); 2978 else if (derived.hasIsModifierReasonElement()) 2979 derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true); 2980 } 2981 2982 if (derived.hasBinding()) { 2983 if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 2984 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 2985 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)); 2986// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 2987 else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) { 2988 ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet()); 2989 ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet()); 2990 if (baseVs == null) { 2991 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 2992 } else if (contextVs == null) { 2993 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 2994 } else { 2995 ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false); 2996 ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false); 2997 if (expBase.getValueset() == null) 2998 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 2999 else if (expDerived.getValueset() == null) 3000 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 3001 else if (ToolingExtensions.hasExtension(expBase.getValueset().getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY)) 3002 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)); 3003 else if (!isSubset(expBase.getValueset(), expDerived.getValueset())) 3004 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)); 3005 } 3006 } 3007 ElementDefinitionBindingComponent d = derived.getBinding(); 3008 ElementDefinitionBindingComponent nb = base.getBinding().copy(); 3009 if (!COPY_BINDING_EXTENSIONS) { 3010 nb.getExtension().clear(); 3011 } 3012 nb.setDescription(null); 3013 nb.getExtension().addAll(d.getExtension()); 3014 if (d.hasStrength()) { 3015 nb.setStrength(d.getStrength()); 3016 } 3017 if (d.hasDescription()) { 3018 nb.setDescription(d.getDescription()); 3019 } 3020 if (d.hasValueSet()) { 3021 nb.setValueSet(d.getValueSet()); 3022 } 3023 base.setBinding(nb); 3024 } else if (trimDifferential) 3025 derived.setBinding(null); 3026 else 3027 derived.getBinding().setUserData(DERIVATION_EQUALS, true); 3028 } // else if (base.hasBinding() && doesn't have bindable type ) 3029 // base 3030 3031 if (derived.hasIsSummaryElement()) { 3032 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) { 3033 if (base.hasIsSummary() && !context.getVersion().equals("1.4.0")) // work around a known issue with some 1.4.0 cosntraints 3034 throw new Error(context.formatMessage(I18nConstants.ERROR_IN_PROFILE__AT__BASE_ISSUMMARY___DERIVED_ISSUMMARY__, purl, derived.getPath(), base.getIsSummaryElement().asStringValue(), derived.getIsSummaryElement().asStringValue())); 3035 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 3036 } else if (trimDifferential) 3037 derived.setIsSummaryElement(null); 3038 else 3039 derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true); 3040 } 3041 3042 if (derived.hasType()) { 3043 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 3044 if (base.hasType()) { 3045 for (TypeRefComponent ts : derived.getType()) { 3046 checkTypeDerivation(purl, srcSD, base, derived, ts); 3047 } 3048 } 3049 base.getType().clear(); 3050 for (TypeRefComponent t : derived.getType()) { 3051 TypeRefComponent tt = t.copy(); 3052// tt.setUserData(DERIVATION_EQUALS, true); 3053 base.getType().add(tt); 3054 } 3055 } 3056 else if (trimDifferential) 3057 derived.getType().clear(); 3058 else 3059 for (TypeRefComponent t : derived.getType()) 3060 t.setUserData(DERIVATION_EQUALS, true); 3061 } 3062 3063 if (derived.hasMapping()) { 3064 // todo: mappings are not cumulative - one replaces another 3065 if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) { 3066 for (ElementDefinitionMappingComponent s : derived.getMapping()) { 3067 boolean found = false; 3068 for (ElementDefinitionMappingComponent d : base.getMapping()) { 3069 found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap())); 3070 } 3071 if (!found) { 3072 base.getMapping().add(s); 3073 } 3074 } 3075 } 3076 else if (trimDifferential) { 3077 derived.getMapping().clear(); 3078 } else { 3079 for (ElementDefinitionMappingComponent t : derived.getMapping()) { 3080 t.setUserData(DERIVATION_EQUALS, true); 3081 } 3082 } 3083 } 3084 for (ElementDefinitionMappingComponent m : base.getMapping()) { 3085 if (m.hasMap()) { 3086 m.setMap(m.getMap().trim()); 3087 } 3088 } 3089 3090 // todo: constraints are cumulative. there is no replacing 3091 for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 3092 s.setUserData(IS_DERIVED, true); 3093 if (!s.hasSource()) { 3094 s.setSource(srcSD.getUrl()); 3095 } 3096 } 3097 if (derived.hasConstraint()) { 3098 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 3099 if (!base.hasConstraint(s.getKey())) { 3100 ElementDefinitionConstraintComponent inv = s.copy(); 3101 base.getConstraint().add(inv); 3102 } 3103 } 3104 } 3105 for (IdType id : derived.getCondition()) { 3106 if (!base.hasCondition(id)) { 3107 base.getCondition().add(id); 3108 } 3109 } 3110 3111 // now, check that we still have a bindable type; if not, delete the binding - see task 8477 3112 if (dest.hasBinding() && !hasBindableType(dest)) { 3113 dest.setBinding(null); 3114 } 3115 3116 // finally, we copy any extensions from source to dest 3117 for (Extension ex : derived.getExtension()) { 3118 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ex.getUrl()); 3119 if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) { 3120 ToolingExtensions.removeExtension(dest, ex.getUrl()); 3121 } 3122 dest.addExtension(ex.copy()); 3123 } 3124 } 3125 if (dest.hasFixed()) { 3126 checkTypeOk(dest, dest.getFixed().fhirType(), srcSD); 3127 } 3128 if (dest.hasPattern()) { 3129 checkTypeOk(dest, dest.getPattern().fhirType(), srcSD); 3130 } 3131 } 3132 3133 public void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts) { 3134 boolean ok = false; 3135 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3136 String t = ts.getWorkingCode(); 3137 for (TypeRefComponent td : base.getType()) {; 3138 String tt = td.getWorkingCode(); 3139 b.append(tt); 3140 if (td.hasCode() && (tt.equals(t))) { 3141 ok = true; 3142 } 3143 if (!ok) { 3144 StructureDefinition sdt = context.fetchTypeDefinition(tt); 3145 if (sdt != null && (sdt.getAbstract() || sdt.getKind() == StructureDefinitionKind.LOGICAL)) { 3146 StructureDefinition sdb = context.fetchTypeDefinition(t); 3147 while (sdb != null && !ok) { 3148 ok = sdb.getType().equals(sdt.getType()); 3149 sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition()); 3150 } 3151 } 3152 } 3153 // work around for old badly generated SDs 3154 if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) { 3155 ok = true; 3156 } 3157 if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) { 3158 ok = true; 3159 } 3160 if (ok && ts.hasTargetProfile()) { 3161 // check that any derived target has a reference chain back to one of the base target profiles 3162 for (UriType u : ts.getTargetProfile()) { 3163 String url = u.getValue(); 3164 boolean tgtOk = !td.hasTargetProfile() || td.hasTargetProfile(url); 3165 while (url != null && !tgtOk) { 3166 StructureDefinition sd = context.fetchRawProfile(url); 3167 if (sd == null) { 3168 if (messages != null) { 3169 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, purl+"#"+derived.getPath(), "Cannot check whether the target profile "+url+" is valid constraint on the base because it is not known", IssueSeverity.WARNING)); 3170 } 3171 url = null; 3172 tgtOk = true; // suppress error message 3173 } else { 3174 url = sd.getBaseDefinition(); 3175 tgtOk = td.hasTargetProfile(url); 3176 } 3177 } 3178 if (!tgtOk) { 3179 if (messages == null) { 3180 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT__THE_TARGET_PROFILE__IS_NOT__VALID_CONSTRAINT_ON_THE_BASE_, purl, derived.getPath(), url, td.getTargetProfile())); 3181 } else { 3182 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)); 3183 } 3184 } 3185 } 3186 } 3187 } 3188 if (!ok) { 3189 throw new DefinitionException(context.formatMessage(I18nConstants.STRUCTUREDEFINITION__AT__ILLEGAL_CONSTRAINED_TYPE__FROM__IN_, purl, derived.getPath(), t, b.toString(), srcSD.getUrl())); 3190 } 3191 } 3192 3193 3194 public void checkTypeOk(ElementDefinition dest, String ft, StructureDefinition sd) { 3195 boolean ok = false; 3196 Set<String> types = new HashSet<>(); 3197 if (dest.getPath().contains(".")) { 3198 for (TypeRefComponent t : dest.getType()) { 3199 if (t.hasCode()) { 3200 types.add(t.getWorkingCode()); 3201 } 3202 ok = ft.equals(t.getWorkingCode()); 3203 } 3204 } else { 3205 types.add(sd.getType()); 3206 ok = ft.equals(sd.getType()); 3207 3208 } 3209 if (!ok) { 3210 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.CONFLICT, dest.getId(), "The fixed value has type '"+ft+"' which is not valid (valid "+Utilities.pluralize("type", dest.getType().size())+": "+types.toString()+")", IssueSeverity.ERROR)); 3211 } 3212 } 3213 3214 private boolean hasBindableType(ElementDefinition ed) { 3215 for (TypeRefComponent tr : ed.getType()) { 3216 if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code")) { 3217 return true; 3218 } 3219 StructureDefinition sd = context.fetchTypeDefinition(tr.getCode()); 3220 if (sd != null && sd.hasExtension(ToolingExtensions.EXT_BINDING_STYLE)) { 3221 return true; 3222 } 3223 } 3224 return false; 3225 } 3226 3227 3228 private boolean isLargerMax(String derived, String base) { 3229 if ("*".equals(base)) { 3230 return false; 3231 } 3232 if ("*".equals(derived)) { 3233 return true; 3234 } 3235 return Integer.parseInt(derived) > Integer.parseInt(base); 3236 } 3237 3238 3239 private boolean isSubset(ValueSet expBase, ValueSet expDerived) { 3240 return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion()); 3241 } 3242 3243 3244 private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) { 3245 for (ValueSetExpansionContainsComponent cc : contains) { 3246 if (!inExpansion(cc, expansion.getContains())) { 3247 return false; 3248 } 3249 if (!codesInExpansion(cc.getContains(), expansion)) { 3250 return false; 3251 } 3252 } 3253 return true; 3254 } 3255 3256 3257 private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) { 3258 for (ValueSetExpansionContainsComponent cc1 : contains) { 3259 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) { 3260 return true; 3261 } 3262 if (inExpansion(cc, cc1.getContains())) { 3263 return true; 3264 } 3265 } 3266 return false; 3267 } 3268 3269 public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { 3270 for (ElementDefinition edb : base.getSnapshot().getElement()) { 3271 if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { 3272 ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); 3273 if (edm == null) { 3274 ElementDefinition edd = derived.getDifferential().addElement(); 3275 edd.setPath(edb.getPath()); 3276 edd.setMax("0"); 3277 } else if (edb.hasSlicing()) { 3278 closeChildren(base, edb, derived, edm); 3279 } 3280 } 3281 } 3282 sortDifferential(base, derived, derived.getName(), new ArrayList<String>(), false); 3283 } 3284 3285 private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) { 3286 String path = edb.getPath()+"."; 3287 int baseStart = base.getSnapshot().getElement().indexOf(edb); 3288 int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1); 3289 int diffStart = derived.getDifferential().getElement().indexOf(edm); 3290 int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1); 3291 3292 for (int cBase = baseStart; cBase < baseEnd; cBase++) { 3293 ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); 3294 if (isImmediateChild(edBase, edb)) { 3295 ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd); 3296 if (edMatch == null) { 3297 ElementDefinition edd = derived.getDifferential().addElement(); 3298 edd.setPath(edBase.getPath()); 3299 edd.setMax("0"); 3300 } else { 3301 closeChildren(base, edBase, derived, edMatch); 3302 } 3303 } 3304 } 3305 } 3306 3307 3308 3309 3310 private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) { 3311 String path = ed.getPath()+"."; 3312 while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) { 3313 cursor++; 3314 } 3315 return cursor; 3316 } 3317 3318 3319 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) { 3320 for (ElementDefinition t : list) { 3321 if (t.getPath().equals(ed.getPath())) { 3322 return t; 3323 } 3324 } 3325 return null; 3326 } 3327 3328 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) { 3329 for (int i = start; i < end; i++) { 3330 ElementDefinition t = list.get(i); 3331 if (t.getPath().equals(ed.getPath())) { 3332 return t; 3333 } 3334 } 3335 return null; 3336 } 3337 3338 3339 private boolean isImmediateChild(ElementDefinition ed) { 3340 String p = ed.getPath(); 3341 if (!p.contains(".")) { 3342 return false; 3343 } 3344 p = p.substring(p.indexOf(".")+1); 3345 return !p.contains("."); 3346 } 3347 3348 private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { 3349 String p = candidate.getPath(); 3350 if (!p.contains(".")) 3351 return false; 3352 if (!p.startsWith(base.getPath()+".")) 3353 return false; 3354 p = p.substring(base.getPath().length()+1); 3355 return !p.contains("."); 3356 } 3357 3358 public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException { 3359 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 3360 gen.setTranslator(getTranslator()); 3361 TableModel model = gen.initNormalTable(corePath, false, true, ed.getId()+(full ? "f" : "n"), true); 3362 3363 boolean deep = false; 3364 String m = ""; 3365 boolean vdeep = false; 3366 if (ed.getSnapshot().getElementFirstRep().getIsModifier()) 3367 m = "modifier_"; 3368 for (ElementDefinition eld : ed.getSnapshot().getElement()) { 3369 deep = deep || eld.getPath().contains("Extension.extension."); 3370 vdeep = vdeep || eld.getPath().contains("Extension.extension.extension."); 3371 } 3372 Row r = gen.new Row(); 3373 model.getRows().add(r); 3374 String en; 3375 if (!full) 3376 en = ed.getName(); 3377 else if (ed.getSnapshot().getElement().get(0).getIsModifier()) 3378 en = "modifierExtension"; 3379 else 3380 en = "extension"; 3381 3382 r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null)); 3383 r.getCells().add(gen.new Cell()); 3384 r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null)); 3385 3386 ElementDefinition ved = null; 3387 if (full || vdeep) { 3388 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 3389 3390 r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 3391 List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0)); 3392 for (ElementDefinition child : children) 3393 if (!child.getPath().endsWith(".id")) { 3394 List<StructureDefinition> sdl = new ArrayList<>(); 3395 sdl.add(ed); 3396 genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), sdl, true, defFile, true, full, corePath, imagePath, true, false, false, false, null, false); 3397 } 3398 } else if (deep) { 3399 List<ElementDefinition> children = new ArrayList<ElementDefinition>(); 3400 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 3401 if (ted.getPath().equals("Extension.extension")) 3402 children.add(ted); 3403 } 3404 3405 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 3406 r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 3407 3408 for (ElementDefinition c : children) { 3409 ved = getValueFor(ed, c); 3410 ElementDefinition ued = getUrlFor(ed, c); 3411 if (ved != null && ued != null) { 3412 Row r1 = gen.new Row(); 3413 r.getSubRows().add(r1); 3414 r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null)); 3415 r1.getCells().add(gen.new Cell()); 3416 r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null)); 3417 genTypes(gen, r1, ved, defFile, ed, corePath, imagePath, false, false); 3418 Cell cell = gen.new Cell(); 3419 cell.addMarkdown(c.getDefinition()); 3420 r1.getCells().add(cell); 3421 r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 3422 } 3423 } 3424 } else { 3425 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 3426 if (ted.getPath().startsWith("Extension.value")) 3427 ved = ted; 3428 } 3429 3430 genTypes(gen, r, ved, defFile, ed, corePath, imagePath, false, false); 3431 3432 r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 3433 } 3434 Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null); 3435 Piece cc = gen.new Piece(null, ed.getName()+": ", null); 3436 c.addPiece(gen.new Piece("br")).addPiece(cc); 3437 c.addMarkdown(ed.getDescription()); 3438 3439 if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) { 3440 c.addPiece(gen.new Piece("br")); 3441 BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath()); 3442 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"))); 3443 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null))); 3444 if (ved.getBinding().hasStrength()) { 3445 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null))); 3446 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition()))); 3447 c.getPieces().add(gen.new Piece(null, ")", null)); 3448 } 3449 if (ved.getBinding().hasDescription() && MarkDownProcessor.isSimpleMarkdown(ved.getBinding().getDescription())) { 3450 c.getPieces().add(gen.new Piece(null, ": ", null)); 3451 c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, ved.getBinding().getDescriptionElement()).asStringValue()); 3452 } 3453 } 3454 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null)); 3455 r.getCells().add(c); 3456 3457 try { 3458 return gen.generate(model, corePath, 0, outputTracker); 3459 } catch (org.hl7.fhir.exceptions.FHIRException e) { 3460 throw new FHIRException(e.getMessage(), e); 3461 } 3462 } 3463 3464 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 3465 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 3466 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 3467 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url")) 3468 return ed.getSnapshot().getElement().get(i); 3469 i++; 3470 } 3471 return null; 3472 } 3473 3474 private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) { 3475 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 3476 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 3477 if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value")) 3478 return ed.getSnapshot().getElement().get(i); 3479 i++; 3480 } 3481 return null; 3482 } 3483 3484 private static final int AGG_NONE = 0; 3485 private static final int AGG_IND = 1; 3486 private static final int AGG_GR = 2; 3487 private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false; 3488 3489 private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean mustSupportMode) { 3490 Cell c = gen.new Cell(); 3491 r.getCells().add(c); 3492 if (e.hasContentReference()) { 3493 ElementInStructure ed = getElementByName(profile.getSnapshot().getElement(), e.getContentReference(), profile); 3494 if (ed == null) 3495 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", e.getContentReference()), null)); 3496 else { 3497 if (ed.getSource() == profile) { 3498 c.getPieces().add(gen.new Piece(null, translate("sd.table", "See ", ed.getElement().getPath()), null)); 3499 c.getPieces().add(gen.new Piece("#"+ed.getElement().getPath(), tail(ed.getElement().getPath()), ed.getElement().getPath())); 3500 } else { 3501 c.getPieces().add(gen.new Piece(null, translate("sd.table", "See ", ed.getElement().getPath()), null)); 3502 c.getPieces().add(gen.new Piece(pfx(corePath, ed.getSource().getUserString("path"))+"#"+ed.getElement().getPath(), tail(ed.getElement().getPath())+" ("+ed.getSource().getType()+")", ed.getElement().getPath())); 3503 } 3504 } 3505 return c; 3506 } 3507 List<TypeRefComponent> types = e.getType(); 3508 if (!e.hasType()) { 3509 if (root) { // we'll use base instead of types then 3510 StructureDefinition bsd = profile == null ? null : context.fetchResource(StructureDefinition.class, profile.getBaseDefinition()); 3511 if (bsd != null) { 3512 if (bsd.hasUserData("path")) { 3513 c.getPieces().add(gen.new Piece(Utilities.isAbsoluteUrl(bsd.getUserString("path")) ? bsd.getUserString("path") : imagePath +bsd.getUserString("path"), bsd.getName(), null)); 3514 } else { 3515 c.getPieces().add(gen.new Piece(null, bsd.getName(), null)); 3516 } 3517 } 3518 return c; 3519 } else if (e.hasContentReference()) { 3520 return c; 3521 } else { 3522 ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER); 3523 if (d != null && d.hasType()) { 3524 types = new ArrayList<ElementDefinition.TypeRefComponent>(); 3525 for (TypeRefComponent tr : d.getType()) { 3526 TypeRefComponent tt = tr.copy(); 3527 tt.setUserData(DERIVATION_EQUALS, true); 3528 types.add(tt); 3529 } 3530 } else { 3531 return c; 3532 } 3533 } 3534 } 3535 3536 boolean first = true; 3537 3538 TypeRefComponent tl = null; 3539 for (TypeRefComponent t : types) { 3540 if (!mustSupportMode || allTypesMustSupport(e) || isMustSupport(t)) { 3541 if (first) { 3542 first = false; 3543 } else { 3544 c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null))); 3545 } 3546 tl = t; 3547 if (t.hasTarget()) { 3548 c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null)); 3549 if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) { 3550 c.addPiece(gen.new Piece(null, " ", null)); 3551 c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); 3552 } 3553 c.getPieces().add(gen.new Piece(null, "(", null)); 3554 boolean tfirst = true; 3555 for (CanonicalType u : t.getTargetProfile()) { 3556 if (!mustSupportMode || allProfilesMustSupport(t.getTargetProfile()) || isMustSupport(u)) { 3557 if (tfirst) 3558 tfirst = false; 3559 else 3560 c.addPiece(gen.new Piece(null, " | ", null)); 3561 genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue()); 3562 if (!mustSupportMode && isMustSupport(u) && e.getMustSupport()) { 3563 c.addPiece(gen.new Piece(null, " ", null)); 3564 c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false); 3565 } 3566 } 3567 } 3568 c.getPieces().add(gen.new Piece(null, ")", null)); 3569 if (t.getAggregation().size() > 0) { 3570 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null)); 3571 boolean firstA = true; 3572 for (Enumeration<AggregationMode> a : t.getAggregation()) { 3573 if (firstA = true) 3574 firstA = false; 3575 else 3576 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null)); 3577 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue()))); 3578 } 3579 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null)); 3580 } 3581 } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type 3582 String ref; 3583 boolean pfirst = true; 3584 for (CanonicalType p : t.getProfile()) { 3585 if (!mustSupportMode || allProfilesMustSupport(t.getProfile()) || isMustSupport(p)) { 3586 if (pfirst) { 3587 pfirst = false; 3588 } else { 3589 c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null))); 3590 } 3591 3592 ref = pkp == null ? null : pkp.getLinkForProfile(profile, p.getValue()); 3593 if (ref != null) { 3594 String[] parts = ref.split("\\|"); 3595 if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) { 3596 // c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd 3597 c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode()))); 3598 } else { 3599 // c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode()))); 3600 c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getWorkingCode()))); 3601 } 3602 } else 3603 c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null))); 3604 if (!mustSupportMode && isMustSupport(p) && e.getMustSupport()) { 3605 c.addPiece(gen.new Piece(null, " ", null)); 3606 c.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false); 3607 } 3608 } 3609 } 3610 } else { 3611 String tc = t.getWorkingCode(); 3612 if (Utilities.isAbsoluteUrl(tc)) { 3613 StructureDefinition sd = context.fetchTypeDefinition(tc); 3614 if (sd == null) { 3615 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null))); 3616 } else { 3617 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), sd.getType(), null))); 3618 } 3619 } else if (pkp != null && pkp.hasLinkFor(tc)) { 3620 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null))); 3621 } else { 3622 c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null))); 3623 } 3624 if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) { 3625 c.addPiece(gen.new Piece(null, " ", null)); 3626 c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); 3627 } 3628 } 3629 } 3630 } 3631 return c; 3632 } 3633 3634 3635 private String pfx(String prefix, String url) { 3636 return Utilities.isAbsoluteUrl(url) ? url : prefix + url; 3637 } 3638 3639 public void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, TypeRefComponent t, String u) { 3640 if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 3641 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u); 3642 if (sd != null) { 3643 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 3644 c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null))); 3645 } else { 3646 String rn = u.substring(40); 3647 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null))); 3648 } 3649 } else if (Utilities.isAbsoluteUrl(u)) { 3650 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u); 3651 if (sd != null) { 3652 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 3653 String ref = pkp.getLinkForProfile(null, sd.getUrl()); 3654 if (ref != null && ref.contains("|")) 3655 ref = ref.substring(0, ref.indexOf("|")); 3656 c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null))); 3657 } else 3658 c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null))); 3659 } else if (t.hasTargetProfile() && u.startsWith("#")) 3660 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.substring(1).toLowerCase()+".html", u, null))); 3661 } 3662 3663 private boolean isProfiledType(List<CanonicalType> theProfile) { 3664 for (CanonicalType next : theProfile){ 3665 if (StringUtils.defaultString(next.getValueAsString()).contains(":")) { 3666 return true; 3667 } 3668 } 3669 return false; 3670 } 3671 3672 3673 private String codeForAggregation(AggregationMode a) { 3674 switch (a) { 3675 case BUNDLED : return "b"; 3676 case CONTAINED : return "c"; 3677 case REFERENCED: return "r"; 3678 default: return "?"; 3679 } 3680 } 3681 3682 private String hintForAggregation(AggregationMode a) { 3683 if (a != null) 3684 return a.getDefinition(); 3685 else 3686 return null; 3687 } 3688 3689 3690 private String checkPrepend(String corePath, String path) { 3691 if (pkp != null && pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:"))) 3692 return corePath+path; 3693 else 3694 return path; 3695 } 3696 3697 3698 private class ElementInStructure { 3699 3700 private StructureDefinition source; 3701 private ElementDefinition element; 3702 3703 public ElementInStructure(StructureDefinition source, ElementDefinition ed) { 3704 this.source = source; 3705 this.element = ed; 3706 } 3707 3708 public StructureDefinition getSource() { 3709 return source; 3710 } 3711 3712 public ElementDefinition getElement() { 3713 return element; 3714 } 3715 3716 } 3717 private ElementInStructure getElementByName(List<ElementDefinition> elements, String contentReference, StructureDefinition source) { 3718 if (contentReference.contains("#")) { 3719 String url = contentReference.substring(0, contentReference.indexOf("#")); 3720 contentReference = contentReference.substring(contentReference.indexOf("#")); 3721 if (!url.equals(source.getUrl())) { 3722 source = context.fetchResource(StructureDefinition.class, url); 3723 if (source == null) { 3724 throw new FHIRException("Unable to resolve StructureDefinition "+url+" resolving content reference "+contentReference); 3725 } 3726 elements = source.getSnapshot().getElement(); 3727 } 3728 } 3729 for (ElementDefinition ed : elements) { 3730 if (("#"+ed.getPath()).equals(contentReference)) { 3731 return new ElementInStructure(source, ed); 3732 } 3733 if (("#"+ed.getId()).equals(contentReference)) { 3734 return new ElementInStructure(source, ed); 3735 } 3736 } 3737 throw new Error("getElementByName: can't find "+contentReference+" in "+elements.toString()+" from "+source.getUrl()); 3738// return null; 3739 } 3740 3741 private ElementDefinitionResolution getElementById(StructureDefinition source, List<ElementDefinition> elements, String contentReference) { 3742 if (!contentReference.startsWith("#") && contentReference.contains("#")) { 3743 String url = contentReference.substring(0, contentReference.indexOf("#")); 3744 contentReference = contentReference.substring(contentReference.indexOf("#")); 3745 if (!url.equals(source.getUrl())){ 3746 source = context.fetchResource(StructureDefinition.class, url); 3747 if (source == null) { 3748 return null; 3749 } 3750 elements = source.getSnapshot().getElement(); 3751 } 3752 } 3753 for (ElementDefinition ed : elements) 3754 if (ed.hasId() && ("#"+ed.getId()).equals(contentReference)) 3755 return new ElementDefinitionResolution(source, ed); 3756 return null; 3757 } 3758 3759 3760 public static String describeExtensionContext(StructureDefinition ext) { 3761 StringBuilder b = new StringBuilder(); 3762 b.append("Use on "); 3763 for (int i = 0; i < ext.getContext().size(); i++) { 3764 StructureDefinitionContextComponent ec = ext.getContext().get(i); 3765 if (i > 0) 3766 b.append(i < ext.getContext().size() - 1 ? ", " : " or "); 3767 b.append(ec.getType().getDisplay()); 3768 b.append(" "); 3769 b.append(ec.getExpression()); 3770 } 3771 if (ext.hasContextInvariant()) { 3772 b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = "); 3773 boolean first = true; 3774 for (StringType s : ext.getContextInvariant()) { 3775 if (first) 3776 first = false; 3777 else 3778 b.append(", "); 3779 b.append("<code>"+s.getValue()+"</code>"); 3780 } 3781 } 3782 return b.toString(); 3783 } 3784 3785 private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) { 3786 IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 3787 StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 3788 if (min.isEmpty() && fallback != null) 3789 min = fallback.getMinElement(); 3790 if (max.isEmpty() && fallback != null) 3791 max = fallback.getMaxElement(); 3792 3793 tracker.used = !max.isEmpty() && !max.getValue().equals("0"); 3794 3795 if (min.isEmpty() && max.isEmpty()) 3796 return null; 3797 else 3798 return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue()); 3799 } 3800 3801 private Cell genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) { 3802 IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 3803 StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 3804 if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 3805 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 3806 if (base.hasMinElement()) { 3807 min = base.getMinElement().copy(); 3808 min.setUserData(DERIVATION_EQUALS, true); 3809 } 3810 } 3811 if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 3812 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 3813 if (base.hasMaxElement()) { 3814 max = base.getMaxElement().copy(); 3815 max.setUserData(DERIVATION_EQUALS, true); 3816 } 3817 } 3818 if (min.isEmpty() && fallback != null) 3819 min = fallback.getMinElement(); 3820 if (max.isEmpty() && fallback != null) 3821 max = fallback.getMaxElement(); 3822 3823 if (!max.isEmpty()) 3824 tracker.used = !max.getValue().equals("0"); 3825 3826 Cell cell = gen.new Cell(null, null, null, null, null); 3827 row.getCells().add(cell); 3828 if (!min.isEmpty() || !max.isEmpty()) { 3829 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null))); 3830 cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null))); 3831 cell.addPiece(checkForNoChange(max, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null))); 3832 } 3833 return cell; 3834 } 3835 3836 3837 private Piece checkForNoChange(Element source, Piece piece) { 3838 if (source.hasUserData(DERIVATION_EQUALS)) { 3839 piece.addStyle("opacity: 0.5"); 3840 } 3841 return piece; 3842 } 3843 3844 private String checkForNoChange(Element source) { 3845 if (source.hasUserData(DERIVATION_EQUALS)) { 3846 return "opacity: 0.5"; 3847 } else { 3848 return null; 3849 } 3850 } 3851 3852 3853 private Piece applyAsUnchanged(Piece piece) { 3854 piece.addStyle("opacity: 0.5"); 3855 return piece; 3856 } 3857 3858 private String applyAsUnchanged() { 3859 return "opacity: 0.5"; 3860 } 3861 3862 3863 private Piece checkForNoChange(Element src1, Element src2, Piece piece) { 3864 if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) { 3865 piece.addStyle("opacity: 0.5"); 3866 } 3867 return piece; 3868 } 3869 3870 public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, 3871 boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean active, boolean mustSupport) throws IOException, FHIRException { 3872 assert(diff != snapshot);// check it's ok to get rid of one of these 3873 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 3874 gen.setTranslator(getTranslator()); 3875 TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), active); 3876 List<ElementDefinition> list = new ArrayList<>(); 3877 if (diff) 3878 list.addAll(profile.getDifferential().getElement()); 3879 else 3880 list.addAll(profile.getSnapshot().getElement()); 3881 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 3882 profiles.add(profile); 3883 if (list.isEmpty()) { 3884 ElementDefinition root = new ElementDefinition().setPath(profile.getType()); 3885 root.setId(profile.getType()); 3886 list.add(root); 3887 } else { 3888 if (list.get(0).getPath().contains(".")) { 3889 ElementDefinition root = new ElementDefinition().setPath(profile.getType()); 3890 root.setId(profile.getType()); 3891 list.add(0, root); 3892 } 3893 } 3894 if (diff) { 3895 insertMissingSparseElements(list); 3896 } 3897 genElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants, null, mustSupport); 3898 try { 3899 return gen.generate(model, imagePath, 0, outputTracker); 3900 } catch (org.hl7.fhir.exceptions.FHIRException e) { 3901 throw new FHIRException(context.formatMessage(I18nConstants.ERROR_GENERATING_TABLE_FOR_PROFILE__, profile.getUrl(), e.getMessage()), e); 3902 } 3903 } 3904 3905 3906 private void insertMissingSparseElements(List<ElementDefinition> list) { 3907 int i = 1; 3908 while (i < list.size()) { 3909 String[] pathCurrent = list.get(i).getPath().split("\\."); 3910 String[] pathLast = list.get(i-1).getPath().split("\\."); 3911 int firstDiff = 0; // the first entry must be a match 3912 while (firstDiff < pathCurrent.length && firstDiff < pathLast.length && pathCurrent[firstDiff].equals(pathLast[firstDiff])) { 3913 firstDiff++; 3914 } 3915 if (!(isSibling(pathCurrent, pathLast, firstDiff) || isChild(pathCurrent, pathLast, firstDiff))) { 3916 // now work backwards down to lastMatch inserting missing path nodes 3917 ElementDefinition parent = findParent(list, i, list.get(i).getPath()); 3918 int parentDepth = Utilities.charCount(parent.getPath(), '.')+1; 3919 int childDepth = Utilities.charCount(list.get(i).getPath(), '.')+1; 3920 if (childDepth > parentDepth + 1) { 3921 String basePath = parent.getPath(); 3922 String baseId = parent.getId(); 3923 for (int index = parentDepth; index >= firstDiff; index--) { 3924 String mtail = makeTail(pathCurrent, parentDepth, index); 3925 ElementDefinition root = new ElementDefinition().setPath(basePath+"."+mtail); 3926 root.setId(baseId+"."+mtail); 3927 list.add(i, root); 3928 } 3929 } 3930 } 3931 i++; 3932 } 3933 } 3934 3935 private ElementDefinition findParent(List<ElementDefinition> list, int i, String path) { 3936 while (i > 0 && !path.startsWith(list.get(i).getPath()+".")) { 3937 i--; 3938 } 3939 return list.get(i); 3940 } 3941 3942 private boolean isSibling(String[] pathCurrent, String[] pathLast, int firstDiff) { 3943 return pathCurrent.length == pathLast.length && firstDiff == pathCurrent.length-1; 3944 } 3945 3946 3947 private boolean isChild(String[] pathCurrent, String[] pathLast, int firstDiff) { 3948 return pathCurrent.length == pathLast.length+1 && firstDiff == pathLast.length; 3949 } 3950 3951 private String makeTail(String[] pathCurrent, int start, int index) { 3952 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("."); 3953 for (int i = start; i <= index; i++) { 3954 b.append(pathCurrent[i]); 3955 } 3956 return b.toString(); 3957 } 3958 3959 private String makePath(String[] pathCurrent, int index) { 3960 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("."); 3961 for (int i = 0; i <= index; i++) { 3962 b.append(pathCurrent[i]); 3963 } 3964 return b.toString(); 3965 } 3966 3967 3968 public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException { 3969 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 3970 gen.setTranslator(getTranslator()); 3971 TableModel model = gen.initGridTable(corePath, profile.getId()); 3972 List<ElementDefinition> list = profile.getSnapshot().getElement(); 3973 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 3974 profiles.add(profile); 3975 genGridElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, true, profileBaseFileName, null, corePath, imagePath, true, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list)); 3976 try { 3977 return gen.generate(model, imagePath, 1, outputTracker); 3978 } catch (org.hl7.fhir.exceptions.FHIRException e) { 3979 throw new FHIRException(e.getMessage(), e); 3980 } 3981 } 3982 3983 3984 private boolean usesMustSupport(List<ElementDefinition> list) { 3985 for (ElementDefinition ed : list) 3986 if (ed.hasMustSupport() && ed.getMustSupport()) 3987 return true; 3988 return false; 3989 } 3990 3991 3992 private Row genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, 3993 boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants, Row slicingRow, boolean mustSupport) throws IOException, FHIRException { 3994 Row originalRow = slicingRow; 3995 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 3996 Row typesRow = null; 3997 3998 List<ElementDefinition> children = getChildren(all, element); 3999// if (!snapshot && isExtension && extensions != null && extensions != isExtension) 4000// return; 4001 4002 if (!onlyInformationIsMapping(all, element)) { 4003 Row row = gen.new Row(); 4004 row.setAnchor(element.getPath()); 4005 row.setColor(getRowColor(element, isConstraintMode)); 4006 if (element.hasSlicing()) 4007 row.setLineColor(1); 4008 else if (element.hasSliceName()) 4009 row.setLineColor(2); 4010 else 4011 row.setLineColor(0); 4012 boolean hasDef = element != null; 4013 boolean ext = false; 4014 if (tail(element.getPath()).equals("extension")) { 4015 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 4016 row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 4017 else 4018 row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 4019 ext = true; 4020 } else if (tail(element.getPath()).equals("modifierExtension")) { 4021 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 4022 row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 4023 else 4024 row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 4025 } else if (!hasDef || element.getType().size() == 0) { 4026 if (root && context.getResourceNames().contains(profile.getType())) { 4027 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 4028 } else { 4029 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 4030 } 4031 } else if (hasDef && element.getType().size() > 1) { 4032 if (allAreReference(element.getType())) { 4033 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 4034 } else { 4035 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 4036 typesRow = row; 4037 } 4038 } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@")) { 4039 row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE); 4040 } else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode())) { 4041 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 4042 } else if (hasDef && element.getType().get(0).hasTarget()) { 4043 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 4044 } else if (hasDef && isDataType(element.getType().get(0).getWorkingCode())) { 4045 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 4046 } else if (hasDef && Utilities.existsInList(element.getType().get(0).getWorkingCode(), "Element", "BackboneElement")) { 4047 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 4048 } else { 4049 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 4050 } 4051 if (element.hasUserData("render.opaque")) { 4052 row.setOpacity("0.5"); 4053 } 4054 UnusedTracker used = new UnusedTracker(); 4055 String ref = defPath == null ? null : defPath + element.getId(); 4056 String sName = tail(element.getPath()); 4057 if (element.hasSliceName()) 4058 sName = sName +":"+element.getSliceName(); 4059 used.used = true; 4060 if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR)) 4061 sName = "@"+sName; 4062 Cell nc = genElementNameCell(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName); 4063 genElementCells(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, nc, mustSupport, true); 4064 if (element.hasSlicing()) { 4065 if (standardExtensionSlicing(element)) { 4066 used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing ... element.hasType() && element.getType().get(0).hasProfile(); 4067 showMissing = false; //? 4068 } else { 4069 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 4070 slicingRow = row; 4071 for (Cell cell : row.getCells()) 4072 for (Piece p : cell.getPieces()) { 4073 p.addStyle("font-style: italic"); 4074 } 4075 } 4076 } else if (element.hasSliceName()) { 4077 row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM); 4078 } 4079 if (used.used || showMissing) 4080 rows.add(row); 4081 if (!used.used && !element.hasSlicing()) { 4082 for (Cell cell : row.getCells()) 4083 for (Piece p : cell.getPieces()) { 4084 p.setStyle("text-decoration:line-through"); 4085 p.setReference(null); 4086 } 4087 } else { 4088 if (slicingRow != originalRow && !children.isEmpty()) { 4089 // we've entered a slice; we're going to create a holder row for the slice children 4090 Row hrow = gen.new Row(); 4091 hrow.setAnchor(element.getPath()); 4092 hrow.setColor(getRowColor(element, isConstraintMode)); 4093 hrow.setLineColor(1); 4094 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 4095 hrow.getCells().add(gen.new Cell(null, null, sName+":All Slices", "", null)); 4096 hrow.getCells().add(gen.new Cell()); 4097 hrow.getCells().add(gen.new Cell()); 4098 hrow.getCells().add(gen.new Cell()); 4099 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null)); 4100 row.getSubRows().add(hrow); 4101 row = hrow; 4102 } 4103 if (typesRow != null && !children.isEmpty()) { 4104 // we've entered a typing slice; we're going to create a holder row for the all types children 4105 Row hrow = gen.new Row(); 4106 hrow.setAnchor(element.getPath()); 4107 hrow.setColor(getRowColor(element, isConstraintMode)); 4108 hrow.setLineColor(1); 4109 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 4110 hrow.getCells().add(gen.new Cell(null, null, sName+":All Types", "", null)); 4111 hrow.getCells().add(gen.new Cell()); 4112 hrow.getCells().add(gen.new Cell()); 4113 hrow.getCells().add(gen.new Cell()); 4114 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null)); 4115 row.getSubRows().add(hrow); 4116 row = hrow; 4117 } 4118 4119 Row currRow = row; 4120 List<ElementChoiceGroup> groups = readChoices(element, children); 4121 boolean isExtension = Utilities.existsInList(tail(element.getPath()), "extension", "modifierExtension"); 4122 for (ElementDefinition child : children) { 4123 if (!child.hasSliceName()) { 4124 currRow = row; 4125 } 4126 Row childRow = chooseChildRowByGroup(gen, currRow, groups, child, element, isConstraintMode); 4127 4128 if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) { 4129 currRow = genElement(defPath, gen, childRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow, mustSupport); 4130 } 4131 } 4132// if (!snapshot && (extensions == null || !extensions)) 4133// for (ElementDefinition child : children) 4134// if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension")) 4135// genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants); 4136 } 4137 if (typesRow != null) { 4138 makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName, mustSupport); 4139 } 4140 } 4141 return slicingRow; 4142 } 4143 4144 private Row chooseChildRowByGroup(HierarchicalTableGenerator gen, Row row, List<ElementChoiceGroup> groups, ElementDefinition element, ElementDefinition parent, boolean isConstraintMode) { 4145 String name = tail(element.getPath()); 4146 for (ElementChoiceGroup grp : groups) { 4147 if (grp.getElements().contains(name)) { 4148 if (grp.getRow() == null) { 4149 grp.setRow(makeChoiceElementRow(gen, row, grp, parent, isConstraintMode)); 4150 } 4151 return grp.getRow(); 4152 } 4153 } 4154 return row; 4155 } 4156 4157 private Row makeChoiceElementRow(HierarchicalTableGenerator gen, Row prow, ElementChoiceGroup grp, ElementDefinition parent, boolean isConstraintMode) { 4158 Row row = gen.new Row(); 4159 row.setAnchor(parent.getPath()+"-"+grp.getName()); 4160 row.setColor(getRowColor(parent, isConstraintMode)); 4161 row.setLineColor(1); 4162 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 4163 row.getCells().add(gen.new Cell(null, null, "(Choice of one)", "", null)); 4164 row.getCells().add(gen.new Cell()); 4165 row.getCells().add(gen.new Cell(null, null, (grp.mandatory ? "1" : "0")+"..1", "", null)); 4166 row.getCells().add(gen.new Cell()); 4167 row.getCells().add(gen.new Cell()); 4168 prow.getSubRows().add(row); 4169 return row; 4170 } 4171 4172 public Cell genElementNameCell(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath, 4173 String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef, 4174 boolean ext, UnusedTracker used, String ref, String sName) throws IOException { 4175 String hint = ""; 4176 hint = checkAdd(hint, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : "")); 4177 if (hasDef && element.hasDefinition()) { 4178 hint = checkAdd(hint, (hasDef && element.hasSliceName() ? ": " : "")); 4179 hint = checkAdd(hint, !hasDef ? null : gt(element.getDefinitionElement())); 4180 } 4181 if (element.hasSlicing()) { 4182 sName = "Slices for "+sName; 4183 } 4184 Cell left = gen.new Cell(null, ref, sName, hint, null); 4185 row.getCells().add(left); 4186 return left; 4187 } 4188 4189 public List<Cell> genElementCells(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath, 4190 String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef, 4191 boolean ext, UnusedTracker used, String ref, String sName, Cell nameCell, boolean mustSupport, boolean allowSubRows) throws IOException { 4192 List<Cell> res = new ArrayList<>(); 4193 Cell gc = gen.new Cell(); 4194 row.getCells().add(gc); 4195 res.add(gc); 4196 if (element != null && element.getIsModifier()) { 4197 checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false)); 4198 } 4199 if (element != null && element.getMustSupport()) { 4200 checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false)); 4201 } 4202 if (element != null && element.getIsSummary()) { 4203 checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false)); 4204 } 4205 if (element != null && (hasNonBaseConstraints(element.getConstraint()) || hasNonBaseConditions(element.getCondition()))) { 4206 gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants ("+listConstraintsAndConditions(element)+")"), "I", null, null, null, false); 4207 } 4208 4209 ExtensionContext extDefn = null; 4210 if (ext) { 4211 if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) { 4212 String eurl = element.getType().get(0).getProfile().get(0).getValue(); 4213 extDefn = locateExtension(StructureDefinition.class, eurl); 4214 if (extDefn == null) { 4215 res.add(genCardinality(gen, element, row, hasDef, used, null)); 4216 res.add(addCell(row, gen.new Cell(null, null, "?gen-e1? "+element.getType().get(0).getProfile(), null, null))); 4217 res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, profile == null ? "" : profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport, allowSubRows)); 4218 } else { 4219 String name = urltail(eurl); 4220 nameCell.getPieces().get(0).setText(name); 4221 // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename")); 4222 nameCell.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl()); 4223 res.add(genCardinality(gen, element, row, hasDef, used, extDefn.getElement())); 4224 ElementDefinition valueDefn = extDefn.getExtensionValueDefinition(); 4225 if (valueDefn != null && !"0".equals(valueDefn.getMax())) 4226 res.add(genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath, root, mustSupport)); 4227 else // if it's complex, we just call it nothing 4228 // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile); 4229 res.add(addCell(row, gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null))); 4230 res.add(generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot, mustSupport, allowSubRows)); 4231 } 4232 } else { 4233 res.add(genCardinality(gen, element, row, hasDef, used, null)); 4234 if ("0".equals(element.getMax())) 4235 res.add(addCell(row, gen.new Cell())); 4236 else 4237 res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport)); 4238 res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport, allowSubRows)); 4239 } 4240 } else { 4241 res.add(genCardinality(gen, element, row, hasDef, used, null)); 4242 if (hasDef && !"0".equals(element.getMax()) && typesRow == null) 4243 res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport)); 4244 else 4245 res.add(addCell(row, gen.new Cell())); 4246 res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport, allowSubRows)); 4247 } 4248 return res; 4249 } 4250 4251 4252 private Cell addCell(Row row, Cell cell) { 4253 row.getCells().add(cell); 4254 return (cell); 4255 } 4256 4257 private String checkAdd(String src, String app) { 4258 return app == null ? src : src + app; 4259 } 4260 4261 private boolean hasNonBaseConditions(List<IdType> conditions) { 4262 for (IdType c : conditions) { 4263 if (!isBaseCondition(c)) { 4264 return true; 4265 } 4266 } 4267 return false; 4268 } 4269 4270 4271 private boolean hasNonBaseConstraints(List<ElementDefinitionConstraintComponent> constraints) { 4272 for (ElementDefinitionConstraintComponent c : constraints) { 4273 if (!isBaseConstraint(c)) { 4274 return true; 4275 } 4276 } 4277 return false; 4278 } 4279 4280 private String listConstraintsAndConditions(ElementDefinition element) { 4281 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 4282 for (ElementDefinitionConstraintComponent con : element.getConstraint()) { 4283 if (!isBaseConstraint(con)) { 4284 b.append(con.getKey()); 4285 } 4286 } 4287 for (IdType id : element.getCondition()) { 4288 if (!isBaseCondition(id)) { 4289 b.append(id.asStringValue()); 4290 } 4291 } 4292 return b.toString(); 4293 } 4294 4295 private boolean isBaseCondition(IdType c) { 4296 String key = c.asStringValue(); 4297 return key.startsWith("ele-") || key.startsWith("res-") || key.startsWith("ext-") || key.startsWith("dom-") || key.startsWith("dr-"); 4298 } 4299 4300 private boolean isBaseConstraint(ElementDefinitionConstraintComponent con) { 4301 String key = con.getKey(); 4302 return key.startsWith("ele-") || key.startsWith("res-") || key.startsWith("ext-") || key.startsWith("dom-") || key.startsWith("dr-"); 4303 } 4304 4305 private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, String corePath, String profileBaseFileName, boolean mustSupportMode) { 4306 // create a child for each choice 4307 for (TypeRefComponent tr : element.getType()) { 4308 if (!mustSupportMode || allTypesMustSupport(element) || isMustSupport(tr)) { 4309 Row choicerow = gen.new Row(); 4310 String t = tr.getWorkingCode(); 4311 if (isReference(t)) { 4312 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null)); 4313 choicerow.getCells().add(gen.new Cell()); 4314 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 4315 choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 4316 Cell c = gen.new Cell(); 4317 choicerow.getCells().add(c); 4318 if (ADD_REFERENCE_TO_TABLE) { 4319 if (tr.getWorkingCode().equals("canonical")) 4320 c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null)); 4321 else 4322 c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null)); 4323 if (!mustSupportMode && isMustSupportDirect(tr) && element.getMustSupport()) { 4324 c.addPiece(gen.new Piece(null, " ", null)); 4325 c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); 4326 } 4327 c.getPieces().add(gen.new Piece(null, "(", null)); 4328 } 4329 boolean first = true; 4330 for (CanonicalType rt : tr.getTargetProfile()) { 4331 if (!mustSupportMode || allProfilesMustSupport(tr.getTargetProfile()) || isMustSupport(rt)) { 4332 if (!first) 4333 c.getPieces().add(gen.new Piece(null, " | ", null)); 4334 genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue()); 4335 if (!mustSupportMode && isMustSupport(rt) && element.getMustSupport()) { 4336 c.addPiece(gen.new Piece(null, " ", null)); 4337 c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false); 4338 } 4339 first = false; 4340 } 4341 } 4342 if (first) { 4343 c.getPieces().add(gen.new Piece(null, "Any", null)); 4344 } 4345 4346 if (ADD_REFERENCE_TO_TABLE) { 4347 c.getPieces().add(gen.new Piece(null, ")", null)); 4348 } 4349 4350 } else { 4351 StructureDefinition sd = context.fetchTypeDefinition(t); 4352 if (sd == null) { 4353 System.out.println("Unable to find "+t); 4354 sd = context.fetchTypeDefinition(t); 4355 } else if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 4356 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 4357 choicerow.getCells().add(gen.new Cell()); 4358 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 4359 choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 4360 Cell c = gen.new Cell(null, corePath+"datatypes.html#"+t, sd.getType(), null, null); 4361 choicerow.getCells().add(c); 4362 if (!mustSupportMode && isMustSupport(tr) && element.getMustSupport()) { 4363 c.addPiece(gen.new Piece(null, " ", null)); 4364 c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); 4365 } 4366 } else { 4367 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 4368 choicerow.getCells().add(gen.new Cell()); 4369 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 4370 choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 4371 Cell c = gen.new Cell(null, pkp.getLinkFor(corePath, t), sd.getType(), null, null); 4372 choicerow.getCells().add(c); 4373 if (!mustSupportMode && isMustSupport(tr) && element.getMustSupport()) { 4374 c.addPiece(gen.new Piece(null, " ", null)); 4375 c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); 4376 } 4377 } 4378 if (tr.hasProfile()) { 4379 Cell typeCell = choicerow.getCells().get(3); 4380 typeCell.addPiece(gen.new Piece(null, "(", null)); 4381 boolean first = true; 4382 for (CanonicalType pt : tr.getProfile()) { 4383 if (!mustSupportMode || allProfilesMustSupport(tr.getProfile()) || isMustSupport(pt)) { 4384 if (first) first = false; else typeCell.addPiece(gen.new Piece(null, " | ", null)); 4385 StructureDefinition psd = context.fetchResource(StructureDefinition.class, pt.getValue()); 4386 if (psd == null) 4387 typeCell.addPiece(gen.new Piece(null, "?gen-e2?", null)); 4388 else 4389 typeCell.addPiece(gen.new Piece(psd.getUserString("path"), psd.getName(), psd.present())); 4390 if (!mustSupportMode && isMustSupport(pt) && element.getMustSupport()) { 4391 typeCell.addPiece(gen.new Piece(null, " ", null)); 4392 typeCell.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false); 4393 } 4394 } 4395 } 4396 typeCell.addPiece(gen.new Piece(null, ")", null)); 4397 } 4398 } 4399 choicerow.getCells().add(gen.new Cell()); 4400 subRows.add(choicerow); 4401 } 4402 } 4403 } 4404 4405 private boolean isReference(String t) { 4406 return t.equals("Reference") || t.equals("canonical"); 4407 } 4408 4409 4410 4411 private void genGridElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, String corePath, String imagePath, boolean root, boolean isConstraintMode) throws IOException, FHIRException { 4412 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 4413 String s = tail(element.getPath()); 4414 List<ElementDefinition> children = getChildren(all, element); 4415 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 4416 4417 if (!onlyInformationIsMapping(all, element)) { 4418 Row row = gen.new Row(); 4419 row.setAnchor(element.getPath()); 4420 row.setColor(getRowColor(element, isConstraintMode)); 4421 if (element.hasSlicing()) 4422 row.setLineColor(1); 4423 else if (element.hasSliceName()) 4424 row.setLineColor(2); 4425 else 4426 row.setLineColor(0); 4427 boolean hasDef = element != null; 4428 String ref = defPath == null ? null : defPath + element.getId(); 4429 UnusedTracker used = new UnusedTracker(); 4430 used.used = true; 4431 Cell left = gen.new Cell(); 4432 if (element.getType().size() == 1 && element.getType().get(0).isPrimitive()) 4433 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold")); 4434 else 4435 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement()))); 4436 if (element.hasSliceName()) { 4437 left.getPieces().add(gen.new Piece("br")); 4438 String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length)); 4439 left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null)); 4440 } 4441 row.getCells().add(left); 4442 4443 ExtensionContext extDefn = null; 4444 genCardinality(gen, element, row, hasDef, used, null); 4445 if (hasDef && !"0".equals(element.getMax())) 4446 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, false); 4447 else 4448 row.getCells().add(gen.new Cell()); 4449 generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null); 4450/* if (element.hasSlicing()) { 4451 if (standardExtensionSlicing(element)) { 4452 used.used = element.hasType() && element.getType().get(0).hasProfile(); 4453 showMissing = false; 4454 } else { 4455 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 4456 row.getCells().get(2).getPieces().clear(); 4457 for (Cell cell : row.getCells()) 4458 for (Piece p : cell.getPieces()) { 4459 p.addStyle("font-style: italic"); 4460 } 4461 } 4462 }*/ 4463 rows.add(row); 4464 for (ElementDefinition child : children) 4465 if (child.getMustSupport()) 4466 genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode); 4467 } 4468 } 4469 4470 4471 private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value) { 4472 if (value.contains("#")) { 4473 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 4474 if (ext == null) 4475 return null; 4476 String tail = value.substring(value.indexOf("#")+1); 4477 ElementDefinition ed = null; 4478 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 4479 if (tail.equals(ted.getSliceName())) { 4480 ed = ted; 4481 return new ExtensionContext(ext, ed); 4482 } 4483 } 4484 return null; 4485 } else { 4486 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 4487 if (ext == null) 4488 return null; 4489 else 4490 return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0)); 4491 } 4492 } 4493 4494 4495 private boolean extensionIsComplex(String value) { 4496 if (value.contains("#")) { 4497 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 4498 if (ext == null) 4499 return false; 4500 String tail = value.substring(value.indexOf("#")+1); 4501 ElementDefinition ed = null; 4502 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 4503 if (tail.equals(ted.getSliceName())) { 4504 ed = ted; 4505 break; 4506 } 4507 } 4508 if (ed == null) 4509 return false; 4510 int i = ext.getSnapshot().getElement().indexOf(ed); 4511 int j = i+1; 4512 while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath())) 4513 j++; 4514 return j - i > 5; 4515 } else { 4516 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 4517 return ext != null && ext.getSnapshot().getElement().size() > 5; 4518 } 4519 } 4520 4521 4522 public String getRowColor(ElementDefinition element, boolean isConstraintMode) { 4523 switch (element.getUserInt(UD_ERROR_STATUS)) { 4524 case STATUS_HINT: return ROW_COLOR_HINT; 4525 case STATUS_WARNING: return ROW_COLOR_WARNING; 4526 case STATUS_ERROR: return ROW_COLOR_ERROR; 4527 case STATUS_FATAL: return ROW_COLOR_FATAL; 4528 } 4529 if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains(".")) 4530 return null; // ROW_COLOR_NOT_MUST_SUPPORT; 4531 else 4532 return null; 4533 } 4534 4535 4536 private String urltail(String path) { 4537 if (path.contains("#")) 4538 return path.substring(path.lastIndexOf('#')+1); 4539 if (path.contains("/")) 4540 return path.substring(path.lastIndexOf('/')+1); 4541 else 4542 return path; 4543 4544 } 4545 4546 private boolean standardExtensionSlicing(ElementDefinition element) { 4547 String t = tail(element.getPath()); 4548 return (t.equals("extension") || t.equals("modifierExtension")) 4549 && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getPath().equals("url") && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE); 4550 } 4551 4552 private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, boolean snapshot, boolean mustSupportOnly, boolean allowSubRows) throws IOException, FHIRException { 4553 return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot, mustSupportOnly, allowSubRows); 4554 } 4555 4556 private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, ElementDefinition valueDefn, boolean snapshot, boolean mustSupportOnly, boolean allowSubRows) throws IOException, FHIRException { 4557 Cell c = gen.new Cell(); 4558 row.getCells().add(c); 4559 4560 if (used) { 4561 if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) { 4562 if (root) { 4563 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 4564 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 4565 } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 4566 !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) { 4567 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 4568 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 4569 } 4570 } 4571 if (root) { 4572 if (profile != null && profile.getAbstract()) { 4573 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4574 c.addPiece(gen.new Piece(null, "This is an abstract profile", null)); 4575 } 4576 } 4577 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 4578 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 4579 } else { 4580 if (definition != null && definition.hasShort()) { 4581 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4582 c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null))); 4583 } else if (fallback != null && fallback.hasShort()) { 4584 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4585 c.addPiece(gen.new Piece(null, gt(fallback.getShortElement()), null).addStyle("opacity: 0.5")); 4586 } 4587 if (url != null) { 4588 if (!c.getPieces().isEmpty()) 4589 c.addPiece(gen.new Piece("br")); 4590 String fullUrl = url.startsWith("#") ? baseURL+url : url; 4591 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 4592 String ref = null; 4593 String ref2 = null; 4594 String fixedUrl = null; 4595 if (ed != null) { 4596 String p = ed.getUserString("path"); 4597 if (p != null) { 4598 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 4599 } 4600 fixedUrl = getFixedUrl(ed); 4601 if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension? 4602 if (fixedUrl.equals(url)) 4603 fixedUrl = null; 4604 else { 4605 StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl); 4606 if (ed2 != null) { 4607 String p2 = ed2.getUserString("path"); 4608 if (p2 != null) { 4609 ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2); 4610 } 4611 } 4612 } 4613 } 4614 } 4615 if (fixedUrl == null) { 4616 c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold")); 4617 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 4618 } else { 4619 // reference to a profile take on the extension show the base URL 4620 c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold")); 4621 c.getPieces().add(gen.new Piece(ref2, fixedUrl, null)); 4622 c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold")); 4623 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 4624 4625 } 4626 } 4627 4628 if (definition.hasSlicing()) { 4629 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4630 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold")); 4631 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 4632 } 4633 if (!definition.getPath().contains(".") && ToolingExtensions.hasExtension(profile, ToolingExtensions.EXT_BINDING_STYLE)) { 4634 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4635 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")); 4636 c.getPieces().add(gen.new Piece(null, "This type can be bound to a value set using the ", null)); 4637 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_BINDING_STYLE), null)); 4638 c.getPieces().add(gen.new Piece(null, " binding style", null)); 4639 4640 } 4641 if (definition.hasExtension(ToolingExtensions.EXT_XML_NAME)) { 4642 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4643 if (definition.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE)) { 4644 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML")+": ", null).addStyle("font-weight:bold")); 4645 c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAME), null)); 4646 c.getPieces().add(gen.new Piece(null, " (", null)); 4647 c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAMESPACE), null)); 4648 c.getPieces().add(gen.new Piece(null, ")", null)); 4649 } else { 4650 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Element Name")+": ", null).addStyle("font-weight:bold")); 4651 c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAME), null)); 4652 } 4653 } else if (definition.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE)) { 4654 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4655 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 4656 c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAMESPACE), null)); 4657 } 4658 if (definition != null) { 4659 ElementDefinitionBindingComponent binding = null; 4660 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 4661 binding = makeUnifiedBinding(valueDefn.getBinding(), valueDefn); 4662 else if (definition.hasBinding()) 4663 binding = makeUnifiedBinding(definition.getBinding(), definition); 4664 if (binding!=null && !binding.isEmpty()) { 4665 if (!c.getPieces().isEmpty()) 4666 c.addPiece(gen.new Piece("br")); 4667 BindingResolution br = pkp == null ? makeNullBr(binding) : pkp.resolveBinding(profile, binding, definition.getPath()); 4668 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"))); 4669 c.getPieces().add(checkForNoChange(binding.getValueSetElement(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null))); 4670 if (binding.hasStrength()) { 4671 c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, " (", null))); 4672 c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition()))); 4673 c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, ")", null))); 4674 } 4675 if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) { 4676 br = pkp == null ? makeNullBr(binding) : pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), definition.getPath()); 4677 c.addPiece(gen.new Piece("br")); 4678 c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-maxvalueset.html", translate("sd.table", "Max Binding")+": ", "Max Value Set Extension").addStyle("font-weight:bold"))); 4679 c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null))); 4680 } 4681 if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) { 4682 br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), definition.getPath()); 4683 c.addPiece(gen.new Piece("br")); 4684 c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-minvalueset.html", translate("sd.table", "Min Binding")+": ", "Min Value Set Extension").addStyle("font-weight:bold"))); 4685 c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null))); 4686 } 4687 if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) { 4688 c.getPieces().add(gen.new Piece(null, ": ", null)); 4689 c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement()).asStringValue(), checkForNoChange(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement()))); 4690 } 4691 } 4692 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 4693 if (!inv.hasSource() || profile == null || inv.getSource().equals(profile.getUrl()) || allInvariants) { 4694 if (!c.getPieces().isEmpty()) 4695 c.addPiece(gen.new Piece("br")); 4696 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 4697 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null))); 4698 } 4699 } 4700 if ((definition.hasBase() && "*".equals(definition.getBase().getMax())) || (definition.hasMax() && "*".equals(definition.getMax()))) { 4701 if (c.getPieces().size() > 0) 4702 c.addPiece(gen.new Piece("br")); 4703 if (definition.hasOrderMeaning()) { 4704 c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null)); 4705 } else { 4706 // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null)); 4707 } 4708 } 4709 if (definition.hasFixed()) { 4710 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4711 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold"))); 4712 if (!useTableForFixedValues || !allowSubRows || definition.getFixed().isPrimitive()) { 4713 String s = buildJson(definition.getFixed()); 4714 String link = null; 4715 if (Utilities.isAbsoluteUrl(s)) 4716 link = pkp.getLinkForUrl(corePath, s); 4717 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 4718 } else { 4719 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "As shown", null).addStyle("color: darkgreen"))); 4720 genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath, false); 4721 } 4722 if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) { 4723 Piece p = describeCoded(gen, definition.getFixed()); 4724 if (p != null) 4725 c.getPieces().add(p); 4726 } 4727 } else if (definition.hasPattern()) { 4728 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4729 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold"))); 4730 if (!useTableForFixedValues || !allowSubRows || definition.getPattern().isPrimitive()) 4731 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 4732 else { 4733 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen"))); 4734 genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath, mustSupportOnly); 4735 } 4736 } else if (definition.hasExample()) { 4737 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 4738 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4739 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel())+": ", null).addStyle("font-weight:bold"))); 4740 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 4741 } 4742 } 4743 if (definition.hasMaxLength() && definition.getMaxLength()!=0) { 4744 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 4745 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 4746 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 4747 } 4748 if (profile != null) { 4749 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 4750 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 4751 ElementDefinitionMappingComponent map = null; 4752 for (ElementDefinitionMappingComponent m : definition.getMapping()) 4753 if (m.getIdentity().equals(md.getIdentity())) 4754 map = m; 4755 if (map != null) { 4756 for (int i = 0; i<definition.getMapping().size(); i++){ 4757 c.addPiece(gen.new Piece("br")); 4758 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null)); 4759 } 4760 } 4761 } 4762 } 4763 } 4764 } 4765 } 4766 } 4767 return c; 4768 } 4769 4770 private BindingResolution makeNullBr(ElementDefinitionBindingComponent binding) { 4771 BindingResolution br = new BindingResolution(); 4772 br.url = "http://none.none/none"; 4773 br.display = "todo"; 4774 return br; 4775 } 4776 4777 private ElementDefinitionBindingComponent makeUnifiedBinding(ElementDefinitionBindingComponent binding, ElementDefinition element) { 4778 if (!element.hasUserData(DERIVATION_POINTER)) { 4779 return binding; 4780 } 4781 ElementDefinition base = (ElementDefinition) element.getUserData(DERIVATION_POINTER); 4782 if (!base.hasBinding()) { 4783 return binding; 4784 } 4785 ElementDefinitionBindingComponent o = base.getBinding(); 4786 ElementDefinitionBindingComponent b = new ElementDefinitionBindingComponent(); 4787 b.setUserData(DERIVATION_POINTER, o); 4788 if (binding.hasValueSet()) { 4789 b.setValueSet(binding.getValueSet()); 4790 } else if (o.hasValueSet()) { 4791 b.setValueSet(o.getValueSet()); 4792 b.getValueSetElement().setUserData(DERIVATION_EQUALS, o.getValueSetElement()); 4793 } 4794 if (binding.hasStrength()) { 4795 b.setStrength(binding.getStrength()); 4796 } else if (o.hasStrength()) { 4797 b.setStrength(o.getStrength()); 4798 b.getStrengthElement().setUserData(DERIVATION_EQUALS, o.getStrengthElement()); 4799 } 4800 if (binding.hasDescription()) { 4801 b.setDescription(binding.getDescription()); 4802 } else if (o.hasDescription()) { 4803 b.setDescription(o.getDescription()); 4804 b.getDescriptionElement().setUserData(DERIVATION_EQUALS, o.getDescriptionElement()); 4805 } 4806 return b; 4807 } 4808 4809 private void genFixedValue(HierarchicalTableGenerator gen, Row erow, DataType value, boolean snapshot, boolean pattern, String corePath, boolean skipnoValue) { 4810 String ref = pkp.getLinkFor(corePath, value.fhirType()); 4811 if (ref != null && ref.contains(".html")) { 4812 ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#"; 4813 } else { 4814 ref = "?gen-fv?"; 4815 } 4816 StructureDefinition sd = context.fetchTypeDefinition(value.fhirType()); 4817 4818 for (org.hl7.fhir.r5.model.Property t : value.children()) { 4819 if (t.getValues().size() > 0 || snapshot) { 4820 ElementDefinition ed = findElementDefinition(sd, t.getName()); 4821 if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) { 4822 if (!skipnoValue) { 4823 Row row = gen.new Row(); 4824 erow.getSubRows().add(row); 4825 Cell c = gen.new Cell(); 4826 row.getCells().add(c); 4827 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+(VersionUtilities.isR5Ver(context.getVersion()) ? "types-definitions.html#"+ed.getBase().getPath() : "element-definitions.html#"+ed.getBase().getPath())), t.getName(), null)); 4828 c = gen.new Cell(); 4829 row.getCells().add(c); 4830 c.addPiece(gen.new Piece(null, null, null)); 4831 c = gen.new Cell(); 4832 row.getCells().add(c); 4833 if (!pattern) { 4834 c.addPiece(gen.new Piece(null, "0..0", null)); 4835 row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/); 4836 } else if (isPrimitive(t.getTypeCode())) { 4837 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 4838 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 4839 } else if (isReference(t.getTypeCode())) { 4840 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 4841 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 4842 } else { 4843 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 4844 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 4845 } 4846 c = gen.new Cell(); 4847 row.getCells().add(c); 4848 if (t.getTypeCode().contains("(")) { 4849 String tc = t.getTypeCode(); 4850 String tn = tc.substring(0, tc.indexOf("(")); 4851 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, tn), tn, null)); 4852 c.addPiece(gen.new Piece(null, "(", null)); 4853 String[] p = tc.substring(tc.indexOf("(")+1, tc.indexOf(")")).split("\\|"); 4854 for (String s : p) { 4855 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, s), s, null)); 4856 } 4857 c.addPiece(gen.new Piece(null, ")", null)); 4858 } else { 4859 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null)); 4860 } 4861 c = gen.new Cell(); 4862 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 4863 row.getCells().add(c); 4864 } 4865 } else { 4866 for (Base b : t.getValues()) { 4867 Row row = gen.new Row(); 4868 erow.getSubRows().add(row); 4869 row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/); 4870 4871 Cell c = gen.new Cell(); 4872 row.getCells().add(c); 4873 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : (VersionUtilities.isThisOrLater("4.1", context.getVersion()) ? corePath+"types-definitions.html#"+ed.getBase().getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath())), t.getName(), null)); 4874 4875 c = gen.new Cell(); 4876 row.getCells().add(c); 4877 c.addPiece(gen.new Piece(null, null, null)); 4878 4879 c = gen.new Cell(); 4880 row.getCells().add(c); 4881 if (pattern) 4882 c.addPiece(gen.new Piece(null, "1.."+(t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 4883 else 4884 c.addPiece(gen.new Piece(null, "1..1", null)); 4885 4886 c = gen.new Cell(); 4887 row.getCells().add(c); 4888 if (b.fhirType().contains("(")) { 4889 String tc = b.fhirType(); 4890 String tn = tc.substring(0, tc.indexOf("(")); 4891 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, tn), tn, null)); 4892 c.addPiece(gen.new Piece(null, "(", null)); 4893 String[] p = tc.substring(tc.indexOf("(")+1, tc.indexOf(")")).split("\\|"); 4894 for (String s : p) { 4895 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, s), s, null)); 4896 } 4897 c.addPiece(gen.new Piece(null, ")", null)); 4898 } else { 4899 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, b.fhirType()), b.fhirType(), null)); 4900 } 4901 4902 if (b.isPrimitive()) { 4903 c = gen.new Cell(); 4904 row.getCells().add(c); 4905 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 4906 c.addPiece(gen.new Piece("br")); 4907 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 4908 String s = b.primitiveValue(); 4909 // ok. let's see if we can find a relevant link for this 4910 String link = null; 4911 if (Utilities.isAbsoluteUrl(s)) { 4912 link = pkp.getLinkForUrl(corePath, s); 4913 } 4914 c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen")); 4915 } else { 4916 c = gen.new Cell(); 4917 row.getCells().add(c); 4918 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 4919 c.addPiece(gen.new Piece("br")); 4920 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 4921 c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen")); 4922 genFixedValue(gen, row, (DataType) b, snapshot, pattern, corePath, skipnoValue); 4923 } 4924 } 4925 } 4926 } 4927 } 4928 } 4929 4930 4931 private ElementDefinition findElementDefinition(StructureDefinition sd, String name) { 4932 String path = sd.getType()+"."+name; 4933 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 4934 if (ed.getPath().equals(path)) 4935 return ed; 4936 } 4937 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT_, path)); 4938 } 4939 4940 4941 private String getFixedUrl(StructureDefinition sd) { 4942 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 4943 if (ed.getPath().equals("Extension.url")) { 4944 if (ed.hasFixed() && ed.getFixed() instanceof UriType) 4945 return ed.getFixed().primitiveValue(); 4946 } 4947 } 4948 return null; 4949 } 4950 4951 4952 private Piece describeCoded(HierarchicalTableGenerator gen, DataType fixed) { 4953 if (fixed instanceof Coding) { 4954 Coding c = (Coding) fixed; 4955 ValidationResult vr = context.validateCode(terminologyServiceOptions , c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay()); 4956 if (vr.getDisplay() != null) 4957 return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); 4958 } else if (fixed instanceof CodeableConcept) { 4959 CodeableConcept cc = (CodeableConcept) fixed; 4960 for (Coding c : cc.getCoding()) { 4961 ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay()); 4962 if (vr.getDisplay() != null) 4963 return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); 4964 } 4965 } 4966 return null; 4967 } 4968 4969 4970 private boolean hasDescription(DataType fixed) { 4971 if (fixed instanceof Coding) { 4972 return ((Coding) fixed).hasDisplay(); 4973 } else if (fixed instanceof CodeableConcept) { 4974 CodeableConcept cc = (CodeableConcept) fixed; 4975 if (cc.hasText()) 4976 return true; 4977 for (Coding c : cc.getCoding()) 4978 if (c.hasDisplay()) 4979 return true; 4980 } // (fixed instanceof CodeType) || (fixed instanceof Quantity); 4981 return false; 4982 } 4983 4984 4985 private boolean isCoded(DataType fixed) { 4986 return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity); 4987 } 4988 4989 4990 private Cell generateGridDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, ElementDefinition valueDefn) throws IOException, FHIRException { 4991 Cell c = gen.new Cell(); 4992 row.getCells().add(c); 4993 4994 if (used) { 4995 if (definition.hasContentReference()) { 4996 ElementInStructure ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference(), profile); 4997 if (ed == null) 4998 c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null)); 4999 else { 5000 if (ed.getSource() == profile) { 5001 c.getPieces().add(gen.new Piece("#"+ed.getElement().getPath(), "See "+ed.getElement().getPath(), null)); 5002 } else { 5003 c.getPieces().add(gen.new Piece(ed.getSource().getUserData("path")+"#"+ed.getElement().getPath(), "See "+ed.getSource().getType()+"."+ed.getElement().getPath(), null)); 5004 } 5005 } 5006 } 5007 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 5008 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 5009 } else { 5010 if (url != null) { 5011 if (!c.getPieces().isEmpty()) 5012 c.addPiece(gen.new Piece("br")); 5013 String fullUrl = url.startsWith("#") ? baseURL+url : url; 5014 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 5015 String ref = null; 5016 if (ed != null) { 5017 String p = ed.getUserString("path"); 5018 if (p != null) { 5019 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 5020 } 5021 } 5022 c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold")); 5023 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 5024 } 5025 5026 if (definition.hasSlicing()) { 5027 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 5028 c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); 5029 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 5030 } 5031 if (definition != null) { 5032 ElementDefinitionBindingComponent binding = null; 5033 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 5034 binding = valueDefn.getBinding(); 5035 else if (definition.hasBinding()) 5036 binding = definition.getBinding(); 5037 if (binding!=null && !binding.isEmpty()) { 5038 if (!c.getPieces().isEmpty()) 5039 c.addPiece(gen.new Piece("br")); 5040 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 5041 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold"))); 5042 c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null))); 5043 if (binding.hasStrength()) { 5044 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 5045 c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), binding.getStrength().toCode(), binding.getStrength().getDefinition()))); c.getPieces().add(gen.new Piece(null, ")", null)); 5046 } 5047 if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) { 5048 c.getPieces().add(gen.new Piece(null, ": ", null)); 5049 c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement()).asStringValue()); 5050 } 5051 } 5052 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 5053 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 5054 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 5055 if (inv.getHumanElement().hasExtension("http://hl7.org/fhir/StructureDefinition/rendering-markdown")) { 5056 c.addMarkdown(inv.getHumanElement().getExtensionString("http://hl7.org/fhir/StructureDefinition/rendering-markdown")); 5057 } else { 5058 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); 5059 } 5060 } 5061 if (definition.hasFixed()) { 5062 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 5063 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); 5064 String s = buildJson(definition.getFixed()); 5065 String link = null; 5066 if (Utilities.isAbsoluteUrl(s)) 5067 link = pkp.getLinkForUrl(corePath, s); 5068 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 5069 } else if (definition.hasPattern()) { 5070 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 5071 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); 5072 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 5073 } else if (definition.hasExample()) { 5074 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 5075 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 5076 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold"))); 5077 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 5078 } 5079 } 5080 if (definition.hasMaxLength() && definition.getMaxLength()!=0) { 5081 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 5082 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 5083 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 5084 } 5085 if (profile != null) { 5086 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 5087 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 5088 ElementDefinitionMappingComponent map = null; 5089 for (ElementDefinitionMappingComponent m : definition.getMapping()) 5090 if (m.getIdentity().equals(md.getIdentity())) 5091 map = m; 5092 if (map != null) { 5093 for (int i = 0; i<definition.getMapping().size(); i++){ 5094 c.addPiece(gen.new Piece("br")); 5095 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null)); 5096 } 5097 } 5098 } 5099 } 5100 } 5101 if (definition.hasDefinition()) { 5102 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 5103 c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold")); 5104 c.addPiece(gen.new Piece("br")); 5105 c.addMarkdown(definition.getDefinition()); 5106// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 5107 } 5108 if (definition.getComment()!=null) { 5109 if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } 5110 c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold")); 5111 c.addPiece(gen.new Piece("br")); 5112 c.addMarkdown(definition.getComment()); 5113// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 5114 } 5115 } 5116 } 5117 } 5118 return c; 5119 } 5120 5121 5122 5123 private String buildJson(DataType value) throws IOException { 5124 if (value instanceof PrimitiveType) 5125 return ((PrimitiveType) value).asStringValue(); 5126 5127 IParser json = context.newJsonParser(); 5128 return json.composeString(value, null); 5129 } 5130 5131 5132 public String describeSlice(ElementDefinitionSlicingComponent slicing) { 5133 return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator())); 5134 } 5135 5136 private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) { 5137 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 5138 for (ElementDefinitionSlicingDiscriminatorComponent id : list) 5139 c.append((id.hasType() ? id.getType().toCode() : "??")+":"+id.getPath()); 5140 return c.toString(); 5141 } 5142 5143 5144 private String describe(SlicingRules rules) { 5145 if (rules == null) 5146 return translate("sd.table", "Unspecified"); 5147 switch (rules) { 5148 case CLOSED : return translate("sd.table", "Closed"); 5149 case OPEN : return translate("sd.table", "Open"); 5150 case OPENATEND : return translate("sd.table", "Open At End"); 5151 default: 5152 return "?gen-sr?"; 5153 } 5154 } 5155 5156 private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) { 5157 return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) && 5158 getChildren(list, e).isEmpty(); 5159 } 5160 5161 private boolean onlyInformationIsMapping(ElementDefinition d) { 5162 return !d.hasShort() && !d.hasDefinition() && 5163 !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() && 5164 !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() && 5165 !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() && 5166 !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() && 5167 !d.hasBinding(); 5168 } 5169 5170 private boolean allAreReference(List<TypeRefComponent> types) { 5171 for (TypeRefComponent t : types) { 5172 if (!t.hasTarget()) 5173 return false; 5174 } 5175 return true; 5176 } 5177 5178 private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) { 5179 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 5180 int i = all.indexOf(element)+1; 5181 while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) { 5182 if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains(".")) 5183 result.add(all.get(i)); 5184 i++; 5185 } 5186 return result; 5187 } 5188 5189 private String tail(String path) { 5190 if (path.contains(".")) 5191 return path.substring(path.lastIndexOf('.')+1); 5192 else 5193 return path; 5194 } 5195 5196 private boolean isDataType(String value) { 5197 StructureDefinition sd = context.fetchTypeDefinition(value); 5198 if (sd == null) // might be running before all SDs are available 5199 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", 5200 "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext"); 5201 else 5202 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 5203 } 5204 5205 private boolean isConstrainedDataType(String value) { 5206 StructureDefinition sd = context.fetchTypeDefinition(value); 5207 if (sd == null) // might be running before all SDs are available 5208 return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"); 5209 else 5210 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT; 5211 } 5212 5213 private String baseType(String value) { 5214 StructureDefinition sd = context.fetchTypeDefinition(value); 5215 if (sd != null) // might be running before all SDs are available 5216 return sd.getType(); 5217 if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity")) 5218 return "Quantity"; 5219 throw new Error(context.formatMessage(I18nConstants.INTERNAL_ERROR___TYPE_NOT_KNOWN_, value)); 5220 } 5221 5222 5223 public boolean isPrimitive(String value) { 5224 StructureDefinition sd = context.fetchTypeDefinition(value); 5225 if (sd == null) // might be running before all SDs are available 5226 return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid"); 5227 else 5228 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 5229 } 5230 5231// private static String listStructures(StructureDefinition p) { 5232// StringBuilder b = new StringBuilder(); 5233// boolean first = true; 5234// for (ProfileStructureComponent s : p.getStructure()) { 5235// if (first) 5236// first = false; 5237// else 5238// b.append(", "); 5239// if (pkp != null && pkp.hasLinkFor(s.getType())) 5240// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 5241// else 5242// b.append(s.getType()); 5243// } 5244// return b.toString(); 5245// } 5246 5247 5248 public StructureDefinition getProfile(StructureDefinition source, String url) { 5249 StructureDefinition profile = null; 5250 String code = null; 5251 if (url.startsWith("#")) { 5252 profile = source; 5253 code = url.substring(1); 5254 } else if (context != null) { 5255 String[] parts = url.split("\\#"); 5256 profile = context.fetchResource(StructureDefinition.class, parts[0]); 5257 code = parts.length == 1 ? null : parts[1]; 5258 } 5259 if (profile == null) 5260 return null; 5261 if (code == null) 5262 return profile; 5263 for (Resource r : profile.getContained()) { 5264 if (r instanceof StructureDefinition && r.getId().equals(code)) 5265 return (StructureDefinition) r; 5266 } 5267 return null; 5268 } 5269 5270 5271 5272 public static class ElementDefinitionHolder { 5273 private String name; 5274 private ElementDefinition self; 5275 private int baseIndex = 0; 5276 private List<ElementDefinitionHolder> children; 5277 private boolean placeHolder = false; 5278 5279 public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) { 5280 super(); 5281 this.self = self; 5282 this.name = self.getPath(); 5283 this.placeHolder = isPlaceholder; 5284 children = new ArrayList<ElementDefinitionHolder>(); 5285 } 5286 5287 public ElementDefinitionHolder(ElementDefinition self) { 5288 this(self, false); 5289 } 5290 5291 public ElementDefinition getSelf() { 5292 return self; 5293 } 5294 5295 public List<ElementDefinitionHolder> getChildren() { 5296 return children; 5297 } 5298 5299 public int getBaseIndex() { 5300 return baseIndex; 5301 } 5302 5303 public void setBaseIndex(int baseIndex) { 5304 this.baseIndex = baseIndex; 5305 } 5306 5307 public boolean isPlaceHolder() { 5308 return this.placeHolder; 5309 } 5310 5311 @Override 5312 public String toString() { 5313 if (self.hasSliceName()) 5314 return self.getPath()+"("+self.getSliceName()+")"; 5315 else 5316 return self.getPath(); 5317 } 5318 } 5319 5320 public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 5321 5322 private boolean inExtension; 5323 private List<ElementDefinition> snapshot; 5324 private int prefixLength; 5325 private String base; 5326 private String name; 5327 private Set<String> errors = new HashSet<String>(); 5328 5329 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) { 5330 this.inExtension = inExtension; 5331 this.snapshot = snapshot; 5332 this.prefixLength = prefixLength; 5333 this.base = base; 5334 if (Utilities.isAbsoluteUrl(base)) { 5335 this.base = urlTail(base); 5336 } 5337 this.name = name; 5338 } 5339 5340 @Override 5341 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 5342 if (o1.getBaseIndex() == 0) 5343 o1.setBaseIndex(find(o1.getSelf().getPath(), true)); 5344 if (o2.getBaseIndex() == 0) 5345 o2.setBaseIndex(find(o2.getSelf().getPath(), true)); 5346 return o1.getBaseIndex() - o2.getBaseIndex(); 5347 } 5348 5349 private int find(String path, boolean mandatory) { 5350 String op = path; 5351 int lc = 0; 5352 String actual = base+path.substring(prefixLength); 5353 for (int i = 0; i < snapshot.size(); i++) { 5354 String p = snapshot.get(i).getPath(); 5355 if (p.equals(actual)) { 5356 return i; 5357 } 5358 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) { 5359 return i; 5360 } 5361 if (actual.endsWith("[x]") && p.startsWith(actual.substring(0, actual.length()-3)) && !p.substring(actual.length()-3).contains(".")) { 5362 return i; 5363 } 5364 if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) { 5365 String ref = snapshot.get(i).getContentReference(); 5366 if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) { 5367 actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 5368 path = actual; 5369 } else if (ref.startsWith("http:")) { 5370 actual = base+(ref.substring(ref.indexOf("#")+1)+"."+path.substring(p.length()+1)).substring(prefixLength); 5371 path = actual; 5372 } else { 5373 // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that 5374 actual = base+(path.substring(0, path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 5375 path = actual; 5376 } 5377 5378 i = 0; 5379 lc++; 5380 if (lc > MAX_RECURSION_LIMIT) 5381 throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")"); 5382 } 5383 } 5384 if (mandatory) { 5385 if (prefixLength == 0) 5386 errors.add("Differential contains path "+path+" which is not found in the in base "+name); 5387 else 5388 errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+name); 5389 } 5390 return 0; 5391 } 5392 5393 public void checkForErrors(List<String> errorList) { 5394 if (errors.size() > 0) { 5395// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 5396// for (String s : errors) 5397// b.append("StructureDefinition "+name+": "+s); 5398// throw new DefinitionException(b.toString()); 5399 for (String s : errors) 5400 if (s.startsWith("!")) 5401 errorList.add("!StructureDefinition "+name+": "+s.substring(1)); 5402 else 5403 errorList.add("StructureDefinition "+name+": "+s); 5404 } 5405 } 5406 } 5407 5408 5409 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors, boolean errorIfChanges) throws FHIRException { 5410 List<ElementDefinition> original = new ArrayList<>(); 5411 original.addAll(diff.getDifferential().getElement()); 5412 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 5413 int lastCount = diffList.size(); 5414 // first, we move the differential elements into a tree 5415 if (diffList.isEmpty()) 5416 return; 5417 5418 ElementDefinitionHolder edh = null; 5419 int i = 0; 5420 if (diffList.get(0).getPath().contains(".")) { 5421 String newPath = diffList.get(0).getPath().split("\\.")[0]; 5422 ElementDefinition e = new ElementDefinition(newPath); 5423 edh = new ElementDefinitionHolder(e, true); 5424 } else { 5425 edh = new ElementDefinitionHolder(diffList.get(0)); 5426 i = 1; 5427 } 5428 5429 boolean hasSlicing = false; 5430 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 5431 for(ElementDefinition elt : diffList) { 5432 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 5433 hasSlicing = true; 5434 break; 5435 } 5436 paths.add(elt.getPath()); 5437 } 5438 if(!hasSlicing) { 5439 // if Differential does not have slicing then safe to pre-sort the list 5440 // so elements and subcomponents are together 5441 Collections.sort(diffList, new ElementNameCompare()); 5442 } 5443 5444 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 5445 5446 // now, we sort the siblings throughout the tree 5447 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name); 5448 sortElements(edh, cmp, errors); 5449 5450 // now, we serialise them back to a list 5451 List<ElementDefinition> newDiff = new ArrayList<>(); 5452 writeElements(edh, newDiff); 5453 if (errorIfChanges) { 5454 compareDiffs(original, newDiff, errors); 5455 } 5456 diffList.clear(); 5457 diffList.addAll(newDiff); 5458 5459 if (lastCount != diffList.size()) 5460 errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal"); 5461 } 5462 5463 private void compareDiffs(List<ElementDefinition> diffList, List<ElementDefinition> newDiff, List<String> errors) { 5464 if (diffList.size() != newDiff.size()) { 5465 errors.add("The diff list size changed when sorting - was "+diffList.size()+" is now "+newDiff.size()); 5466 } else { 5467 for (int i = 0; i < Integer.min(diffList.size(), newDiff.size()); i++) { 5468 ElementDefinition e = diffList.get(i); 5469 ElementDefinition n = newDiff.get(i); 5470 if (!n.getPath().equals(e.getPath())) { 5471 errors.add("The element "+e.getPath()+" is out of order (and maybe others after it)"); 5472 return; 5473 } 5474 } 5475 } 5476 } 5477 5478 5479 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 5480 String path = edh.getSelf().getPath(); 5481 final String prefix = path + "."; 5482 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 5483 if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) { 5484 String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0]; 5485 ElementDefinition e = new ElementDefinition(newPath); 5486 ElementDefinitionHolder child = new ElementDefinitionHolder(e, true); 5487 edh.getChildren().add(child); 5488 i = processElementsIntoTree(child, i, list); 5489 5490 } else { 5491 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 5492 edh.getChildren().add(child); 5493 i = processElementsIntoTree(child, i+1, list); 5494 } 5495 } 5496 return i; 5497 } 5498 5499 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException { 5500 if (edh.getChildren().size() == 1) 5501 // 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 5502 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false); 5503 else 5504 Collections.sort(edh.getChildren(), cmp); 5505 cmp.checkForErrors(errors); 5506 5507 for (ElementDefinitionHolder child : edh.getChildren()) { 5508 if (child.getChildren().size() > 0) { 5509 ElementDefinitionComparer ccmp = getComparer(cmp, child); 5510 if (ccmp != null) { 5511 sortElements(child, ccmp, errors); 5512 } 5513 } 5514 } 5515 } 5516 5517 5518 public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error { 5519 // what we have to check for here is running off the base profile into a data type profile 5520 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 5521 ElementDefinitionComparer ccmp; 5522 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) { 5523 if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && (child.getSelf().hasType() && child.getSelf().getType().get(0).hasProfile())) { 5524 if (child.getSelf().getType().get(0).getProfile().size() > 1) { 5525 throw new FHIRException(context.formatMessage(I18nConstants.UNHANDLED_SITUATION_RESOURCE_IS_PROFILED_TO_MORE_THAN_ONE_OPTION__CANNOT_SORT_PROFILE)); 5526 } 5527 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 5528 while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 5529 profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition()); 5530 } 5531 if (profile==null) { 5532 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 5533 } else { 5534 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name); 5535 } 5536 } else { 5537 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name); 5538 } 5539 } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) { 5540 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 5541 if (profile==null) 5542 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 5543 else 5544 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name); 5545 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) { 5546 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 5547 if (profile==null) 5548 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 5549 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name); 5550 } else if (child.getSelf().getType().size() == 1) { 5551 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode())); 5552 if (profile==null) 5553 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 5554 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 5555 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 5556 String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2"); 5557 String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2"); 5558 String p = childLastNode.substring(edLastNode.length()-3); 5559 if (isPrimitive(Utilities.uncapitalize(p))) 5560 p = Utilities.uncapitalize(p); 5561 StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p)); 5562 if (sd == null) 5563 throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId())); 5564 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name); 5565 } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) { 5566 for (TypeRefComponent t: child.getSelf().getType()) { 5567 if (!t.getWorkingCode().equals("Reference")) { 5568 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()))); 5569 } 5570 } 5571 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 5572 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 5573 } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) { 5574 for (TypeRefComponent t: ed.getType()) { 5575 if (!t.getWorkingCode().equals("Reference")) { 5576 throw new Error(context.formatMessage(I18nConstants.NOT_HANDLED_YET_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType()))); 5577 } 5578 } 5579 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 5580 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 5581 } else { 5582 // this is allowed if we only profile the extensions 5583 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element")); 5584 if (profile==null) 5585 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath())); 5586 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name); 5587// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 5588 } 5589 return ccmp; 5590 } 5591 5592 private String resolveType(String code) { 5593 if (Utilities.isAbsoluteUrl(code)) { 5594 StructureDefinition sd = context.fetchResource(StructureDefinition.class, code); 5595 if (sd != null) { 5596 return sd.getType(); 5597 } 5598 } 5599 return code; 5600 } 5601 5602 private static String sdNs(String type) { 5603 return sdNs(type, null); 5604 } 5605 5606 public static String sdNs(String type, String overrideVersionNs) { 5607 if (Utilities.isAbsoluteUrl(type)) 5608 return type; 5609 else if (overrideVersionNs != null) 5610 return Utilities.pathURL(overrideVersionNs, type); 5611 else 5612 return "http://hl7.org/fhir/StructureDefinition/"+type; 5613 } 5614 5615 5616 private boolean isAbstract(String code) { 5617 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource"); 5618 } 5619 5620 5621 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 5622 if (!edh.isPlaceHolder()) 5623 list.add(edh.getSelf()); 5624 for (ElementDefinitionHolder child : edh.getChildren()) { 5625 writeElements(child, list); 5626 } 5627 } 5628 5629 /** 5630 * First compare element by path then by name if same 5631 */ 5632 private static class ElementNameCompare implements Comparator<ElementDefinition> { 5633 5634 @Override 5635 public int compare(ElementDefinition o1, ElementDefinition o2) { 5636 String path1 = normalizePath(o1); 5637 String path2 = normalizePath(o2); 5638 int cmp = path1.compareTo(path2); 5639 if (cmp == 0) { 5640 String name1 = o1.hasSliceName() ? o1.getSliceName() : ""; 5641 String name2 = o2.hasSliceName() ? o2.getSliceName() : ""; 5642 cmp = name1.compareTo(name2); 5643 } 5644 return cmp; 5645 } 5646 5647 private static String normalizePath(ElementDefinition e) { 5648 if (!e.hasPath()) return ""; 5649 String path = e.getPath(); 5650 // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc. 5651 // so strip off the [x] suffix when comparing the path names. 5652 if (path.endsWith("[x]")) { 5653 path = path.substring(0, path.length()-3); 5654 } 5655 return path; 5656 } 5657 5658 } 5659 5660 5661 // generate schematrons for the rules in a structure definition 5662 public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException { 5663 if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT) 5664 throw new DefinitionException(context.formatMessage(I18nConstants.NOT_THE_RIGHT_KIND_OF_STRUCTURE_TO_GENERATE_SCHEMATRONS_FOR)); 5665 if (!structure.hasSnapshot()) 5666 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 5667 5668 StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition()); 5669 5670 if (base != null) { 5671 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 5672 5673 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 5674 generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base); 5675 sch.dump(); 5676 } 5677 } 5678 5679 // generate a CSV representation of the structure definition 5680 public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception { 5681 if (!structure.hasSnapshot()) 5682 throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT)); 5683 5684 CSVWriter csv = new CSVWriter(dest, structure, asXml); 5685 5686 for (ElementDefinition child : structure.getSnapshot().getElement()) { 5687 csv.processElement(child); 5688 } 5689 csv.dump(); 5690 } 5691 5692 5693 private class Slicer extends ElementDefinitionSlicingComponent { 5694 String criteria = ""; 5695 String name = ""; 5696 boolean check; 5697 public Slicer(boolean cantCheck) { 5698 super(); 5699 this.check = cantCheck; 5700 } 5701 } 5702 5703 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) { 5704 // given a child in a structure, it's sliced. figure out the slicing xpath 5705 if (child.getPath().endsWith(".extension")) { 5706 ElementDefinition ued = getUrlFor(structure, child); 5707 if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile()))) 5708 return new Slicer(false); 5709 else { 5710 Slicer s = new Slicer(true); 5711 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue(); 5712 s.name = " with URL = '"+url+"'"; 5713 s.criteria = "[@url = '"+url+"']"; 5714 return s; 5715 } 5716 } else 5717 return new Slicer(false); 5718 } 5719 5720 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException { 5721 // generateForChild(txt, structure, child); 5722 List<ElementDefinition> children = getChildList(structure, ed); 5723 String sliceName = null; 5724 ElementDefinitionSlicingComponent slicing = null; 5725 for (ElementDefinition child : children) { 5726 String name = tail(child.getPath()); 5727 if (child.hasSlicing()) { 5728 sliceName = name; 5729 slicing = child.getSlicing(); 5730 } else if (!name.equals(sliceName)) 5731 slicing = null; 5732 5733 ElementDefinition based = getByPath(base, child.getPath()); 5734 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 5735 boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax()))); 5736 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 5737 if (slicer.check) { 5738 if (doMin || doMax) { 5739 Section s = sch.section(xpath); 5740 Rule r = s.rule(xpath); 5741 if (doMin) 5742 r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin())); 5743 if (doMax) 5744 r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax()); 5745 } 5746 } 5747 } 5748 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 5749 if (inv.hasXpath()) { 5750 Section s = sch.section(ed.getPath()); 5751 Rule r = s.rule(xpath); 5752 r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 5753 } 5754 } 5755 if (!ed.hasContentReference()) { 5756 for (ElementDefinition child : children) { 5757 String name = tail(child.getPath()); 5758 generateForChildren(sch, xpath+"/f:"+name, child, structure, base); 5759 } 5760 } 5761 } 5762 5763 5764 5765 5766 private ElementDefinition getByPath(StructureDefinition base, String path) { 5767 for (ElementDefinition ed : base.getSnapshot().getElement()) { 5768 if (ed.getPath().equals(path)) 5769 return ed; 5770 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))) 5771 return ed; 5772 } 5773 return null; 5774 } 5775 5776 5777 public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException { 5778 if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { 5779 if (!sd.hasDifferential()) 5780 sd.setDifferential(new StructureDefinitionDifferentialComponent()); 5781 generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType()); 5782 } 5783 if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { 5784 if (!sd.hasSnapshot()) 5785 sd.setSnapshot(new StructureDefinitionSnapshotComponent()); 5786 generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType()); 5787 } 5788 } 5789 5790 5791 private boolean hasMissingIds(List<ElementDefinition> list) { 5792 for (ElementDefinition ed : list) { 5793 if (!ed.hasId()) 5794 return true; 5795 } 5796 return false; 5797 } 5798 5799 public class SliceList { 5800 5801 private Map<String, String> slices = new HashMap<>(); 5802 5803 public void seeElement(ElementDefinition ed) { 5804 Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator(); 5805 while (iter.hasNext()) { 5806 Map.Entry<String,String> entry = iter.next(); 5807 if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath())) 5808 iter.remove(); 5809 } 5810 5811 if (ed.hasSliceName()) 5812 slices.put(ed.getPath(), ed.getSliceName()); 5813 } 5814 5815 public String[] analyse(List<String> paths) { 5816 String s = paths.get(0); 5817 String[] res = new String[paths.size()]; 5818 res[0] = null; 5819 for (int i = 1; i < paths.size(); i++) { 5820 s = s + "."+paths.get(i); 5821 if (slices.containsKey(s)) 5822 res[i] = slices.get(s); 5823 else 5824 res[i] = null; 5825 } 5826 return res; 5827 } 5828 5829 } 5830 5831 private void generateIds(List<ElementDefinition> list, String name, String type) throws DefinitionException { 5832 if (list.isEmpty()) 5833 return; 5834 5835 Map<String, String> idList = new HashMap<String, String>(); 5836 Map<String, String> replacedIds = new HashMap<String, String>(); 5837 5838 SliceList sliceInfo = new SliceList(); 5839 // first pass, update the element ids 5840 for (ElementDefinition ed : list) { 5841 List<String> paths = new ArrayList<String>(); 5842 if (!ed.hasPath()) 5843 throw new DefinitionException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_DEFINITION__IN_, Integer.toString(list.indexOf(ed)), name)); 5844 sliceInfo.seeElement(ed); 5845 String[] pl = ed.getPath().split("\\."); 5846 for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus 5847 paths.add(pl[i]); 5848 String slices[] = sliceInfo.analyse(paths); 5849 5850 StringBuilder b = new StringBuilder(); 5851 b.append(paths.get(0)); 5852 for (int i = 1; i < paths.size(); i++) { 5853 b.append("."); 5854 String s = paths.get(i); 5855 String p = slices[i]; 5856 b.append(fixChars(s)); 5857 if (p != null) { 5858 b.append(":"); 5859 b.append(p); 5860 } 5861 } 5862 String bs = b.toString(); 5863 if (ed.hasId()) { 5864 replacedIds.put(ed.getId(), ed.getPath()); 5865 } 5866 ed.setId(bs); 5867 if (idList.containsKey(bs)) { 5868 if (exception || messages == null) { 5869 throw new DefinitionException(context.formatMessage(I18nConstants.SAME_ID_ON_MULTIPLE_ELEMENTS__IN_, bs, idList.get(bs), ed.getPath(), name)); 5870 } else 5871 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR)); 5872 } 5873 idList.put(bs, ed.getPath()); 5874 if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) { 5875 String s = ed.getContentReference(); 5876 if (replacedIds.containsKey(s.substring(1))) { 5877 ed.setContentReference("http://hl7.org/fhir/StructureDefinition/"+type+"#"+replacedIds.get(s.substring(1))); 5878 } else { 5879 ed.setContentReference("http://hl7.org/fhir/StructureDefinition/"+type+s); 5880 } 5881 } 5882 } 5883 // second path - fix up any broken path based id references 5884 5885 } 5886 5887 5888 private Object fixChars(String s) { 5889 return s.replace("_", "-"); 5890 } 5891 5892 5893// private String describeExtension(ElementDefinition ed) { 5894// if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) 5895// return ""; 5896// return "$"+urlTail(ed.getTypeFirstRep().getProfile()); 5897// } 5898// 5899 5900 private static String urlTail(String profile) { 5901 return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile; 5902 } 5903 5904 5905 private String checkName(String name) { 5906// if (name.contains(".")) 5907//// throw new Exception("Illegal name "+name+": no '.'"); 5908// if (name.contains(" ")) 5909// throw new Exception("Illegal name "+name+": no spaces"); 5910 StringBuilder b = new StringBuilder(); 5911 for (char c : name.toCharArray()) { 5912 if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']')) 5913 b.append(c); 5914 } 5915 return b.toString().toLowerCase(); 5916 } 5917 5918 5919 private int charCount(String path, char t) { 5920 int res = 0; 5921 for (char ch : path.toCharArray()) { 5922 if (ch == t) 5923 res++; 5924 } 5925 return res; 5926 } 5927 5928// 5929//private void generateForChild(TextStreamWriter txt, 5930// StructureDefinition structure, ElementDefinition child) { 5931// // TODO Auto-generated method stub 5932// 5933//} 5934 5935 private interface ExampleValueAccessor { 5936 DataType getExampleValue(ElementDefinition ed); 5937 String getId(); 5938 } 5939 5940 private class BaseExampleValueAccessor implements ExampleValueAccessor { 5941 @Override 5942 public DataType getExampleValue(ElementDefinition ed) { 5943 if (ed.hasFixed()) 5944 return ed.getFixed(); 5945 if (ed.hasExample()) 5946 return ed.getExample().get(0).getValue(); 5947 else 5948 return null; 5949 } 5950 5951 @Override 5952 public String getId() { 5953 return "-genexample"; 5954 } 5955 } 5956 5957 private class ExtendedExampleValueAccessor implements ExampleValueAccessor { 5958 private String index; 5959 5960 public ExtendedExampleValueAccessor(String index) { 5961 this.index = index; 5962 } 5963 @Override 5964 public DataType getExampleValue(ElementDefinition ed) { 5965 if (ed.hasFixed()) 5966 return ed.getFixed(); 5967 for (Extension ex : ed.getExtension()) { 5968 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 5969 DataType value = ToolingExtensions.getExtension(ex, "exValue").getValue(); 5970 if (index.equals(ndx) && value != null) 5971 return value; 5972 } 5973 return null; 5974 } 5975 @Override 5976 public String getId() { 5977 return "-genexample-"+index; 5978 } 5979 } 5980 5981 public List<org.hl7.fhir.r5.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException { 5982 List<org.hl7.fhir.r5.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r5.elementmodel.Element>(); 5983 if (sd.hasSnapshot()) { 5984 if (evenWhenNoExamples || hasAnyExampleValues(sd)) 5985 examples.add(generateExample(sd, new BaseExampleValueAccessor())); 5986 for (int i = 1; i <= 50; i++) { 5987 if (hasAnyExampleValues(sd, Integer.toString(i))) 5988 examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i)))); 5989 } 5990 } 5991 return examples; 5992 } 5993 5994 private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException { 5995 ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 5996 org.hl7.fhir.r5.elementmodel.Element r = new org.hl7.fhir.r5.elementmodel.Element(ed.getPath(), new Property(context, ed, profile)); 5997 List<ElementDefinition> children = getChildMap(profile, ed); 5998 for (ElementDefinition child : children) { 5999 if (child.getPath().endsWith(".id")) { 6000 org.hl7.fhir.r5.elementmodel.Element id = new org.hl7.fhir.r5.elementmodel.Element("id", new Property(context, child, profile)); 6001 id.setValue(profile.getId()+accessor.getId()); 6002 r.getChildren().add(id); 6003 } else { 6004 org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); 6005 if (e != null) 6006 r.getChildren().add(e); 6007 } 6008 } 6009 return r; 6010 } 6011 6012 private org.hl7.fhir.r5.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException { 6013 DataType v = accessor.getExampleValue(ed); 6014 if (v != null) { 6015 return new ObjectConverter(context).convert(new Property(context, ed, profile), v); 6016 } else { 6017 org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile)); 6018 boolean hasValue = false; 6019 List<ElementDefinition> children = getChildMap(profile, ed); 6020 for (ElementDefinition child : children) { 6021 if (!child.hasContentReference()) { 6022 org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); 6023 if (e != null) { 6024 hasValue = true; 6025 res.getChildren().add(e); 6026 } 6027 } 6028 } 6029 if (hasValue) 6030 return res; 6031 else 6032 return null; 6033 } 6034 } 6035 6036 private boolean hasAnyExampleValues(StructureDefinition sd, String index) { 6037 for (ElementDefinition ed : sd.getSnapshot().getElement()) 6038 for (Extension ex : ed.getExtension()) { 6039 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 6040 Extension exv = ToolingExtensions.getExtension(ex, "exValue"); 6041 if (exv != null) { 6042 DataType value = exv.getValue(); 6043 if (index.equals(ndx) && value != null) 6044 return true; 6045 } 6046 } 6047 return false; 6048 } 6049 6050 6051 private boolean hasAnyExampleValues(StructureDefinition sd) { 6052 for (ElementDefinition ed : sd.getSnapshot().getElement()) 6053 if (ed.hasExample()) 6054 return true; 6055 return false; 6056 } 6057 6058 6059 public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException { 6060 sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy()); 6061 6062 if (sd.hasBaseDefinition()) { 6063 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 6064 if (base == null) 6065 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE_DEFINITION_FOR_LOGICAL_MODEL__FROM_, sd.getBaseDefinition(), sd.getUrl())); 6066 copyElements(sd, base.getSnapshot().getElement()); 6067 } 6068 copyElements(sd, sd.getDifferential().getElement()); 6069 } 6070 6071 6072 private void copyElements(StructureDefinition sd, List<ElementDefinition> list) { 6073 for (ElementDefinition ed : list) { 6074 if (ed.getPath().contains(".")) { 6075 ElementDefinition n = ed.copy(); 6076 n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1)); 6077 sd.getSnapshot().addElement(n); 6078 } 6079 } 6080 } 6081 6082 6083 public void cleanUpDifferential(StructureDefinition sd) { 6084 if (sd.getDifferential().getElement().size() > 1) 6085 cleanUpDifferential(sd, 1); 6086 } 6087 6088 private void cleanUpDifferential(StructureDefinition sd, int start) { 6089 int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.'); 6090 int c = start; 6091 int len = sd.getDifferential().getElement().size(); 6092 HashSet<String> paths = new HashSet<String>(); 6093 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) { 6094 ElementDefinition ed = sd.getDifferential().getElement().get(c); 6095 if (!paths.contains(ed.getPath())) { 6096 paths.add(ed.getPath()); 6097 int ic = c+1; 6098 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 6099 ic++; 6100 ElementDefinition slicer = null; 6101 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 6102 slices.add(ed); 6103 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) { 6104 ElementDefinition edi = sd.getDifferential().getElement().get(ic); 6105 if (ed.getPath().equals(edi.getPath())) { 6106 if (slicer == null) { 6107 slicer = new ElementDefinition(); 6108 slicer.setPath(edi.getPath()); 6109 slicer.getSlicing().setRules(SlicingRules.OPEN); 6110 sd.getDifferential().getElement().add(c, slicer); 6111 c++; 6112 ic++; 6113 } 6114 slices.add(edi); 6115 } 6116 ic++; 6117 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 6118 ic++; 6119 } 6120 // now we're at the end, we're going to figure out the slicing discriminator 6121 if (slicer != null) 6122 determineSlicing(slicer, slices); 6123 } 6124 c++; 6125 if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) { 6126 cleanUpDifferential(sd, c); 6127 c++; 6128 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 6129 c++; 6130 } 6131 } 6132 } 6133 6134 6135 private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) { 6136 // first, name them 6137 int i = 0; 6138 for (ElementDefinition ed : slices) { 6139 if (ed.hasUserData("slice-name")) { 6140 ed.setSliceName(ed.getUserString("slice-name")); 6141 } else { 6142 i++; 6143 ed.setSliceName("slice-"+Integer.toString(i)); 6144 } 6145 } 6146 // now, the hard bit, how are they differentiated? 6147 // right now, we hard code this... 6148 if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension")) 6149 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 6150 else if (slicer.getPath().equals("DiagnosticReport.result")) 6151 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code"); 6152 else if (slicer.getPath().equals("Observation.related")) 6153 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code"); 6154 else if (slicer.getPath().equals("Bundle.entry")) 6155 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile"); 6156 else 6157 throw new Error("No slicing for "+slicer.getPath()); 6158 } 6159 6160 public class SpanEntry { 6161 private List<SpanEntry> children = new ArrayList<SpanEntry>(); 6162 private boolean profile; 6163 private String id; 6164 private String name; 6165 private String resType; 6166 private String cardinality; 6167 private String description; 6168 private String profileLink; 6169 private String resLink; 6170 private String type; 6171 6172 public String getName() { 6173 return name; 6174 } 6175 public void setName(String name) { 6176 this.name = name; 6177 } 6178 public String getResType() { 6179 return resType; 6180 } 6181 public void setResType(String resType) { 6182 this.resType = resType; 6183 } 6184 public String getCardinality() { 6185 return cardinality; 6186 } 6187 public void setCardinality(String cardinality) { 6188 this.cardinality = cardinality; 6189 } 6190 public String getDescription() { 6191 return description; 6192 } 6193 public void setDescription(String description) { 6194 this.description = description; 6195 } 6196 public String getProfileLink() { 6197 return profileLink; 6198 } 6199 public void setProfileLink(String profileLink) { 6200 this.profileLink = profileLink; 6201 } 6202 public String getResLink() { 6203 return resLink; 6204 } 6205 public void setResLink(String resLink) { 6206 this.resLink = resLink; 6207 } 6208 public String getId() { 6209 return id; 6210 } 6211 public void setId(String id) { 6212 this.id = id; 6213 } 6214 public boolean isProfile() { 6215 return profile; 6216 } 6217 public void setProfile(boolean profile) { 6218 this.profile = profile; 6219 } 6220 public List<SpanEntry> getChildren() { 6221 return children; 6222 } 6223 public String getType() { 6224 return type; 6225 } 6226 public void setType(String type) { 6227 this.type = type; 6228 } 6229 6230 } 6231 6232 public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException { 6233 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true); 6234 gen.setTranslator(getTranslator()); 6235 TableModel model = initSpanningTable(gen, "", false, profile.getId()); 6236 Set<String> processed = new HashSet<String>(); 6237 SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix); 6238 6239 genSpanEntry(gen, model.getRows(), span); 6240 return gen.generate(model, "", 0, outputTracker); 6241 } 6242 6243 private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException { 6244 SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile); 6245 boolean wantProcess = !processed.contains(profile.getUrl()); 6246 processed.add(profile.getUrl()); 6247 if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 6248 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 6249 if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) { 6250 String card = getCardinality(ed, profile.getSnapshot().getElement()); 6251 if (!card.endsWith(".0")) { 6252 List<String> refProfiles = listReferenceProfiles(ed); 6253 if (refProfiles.size() > 0) { 6254 String uri = refProfiles.get(0); 6255 if (uri != null) { 6256 StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri); 6257 if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) { 6258 res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix)); 6259 } 6260 } 6261 } 6262 } 6263 } 6264 } 6265 } 6266 return res; 6267 } 6268 6269 6270 private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) { 6271 int min = ed.getMin(); 6272 int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax()); 6273 ElementDefinition ned = ed; 6274 while (ned != null && ned.getPath().contains(".")) { 6275 ned = findParent(ned, list); 6276 if (ned != null) { // todo: this can happen if we've walked into a resoruce. Not sure what to about that? 6277 if ("0".equals(ned.getMax())) 6278 max = 0; 6279 else if (!ned.getMax().equals("1") && !ned.hasSlicing()) 6280 max = Integer.MAX_VALUE; 6281 if (ned.getMin() == 0) { 6282 min = 0; 6283 } 6284 } 6285 } 6286 return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max)); 6287 } 6288 6289 6290 private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) { 6291 int i = list.indexOf(ed)-1; 6292 while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+".")) 6293 i--; 6294 if (i == -1) 6295 return null; 6296 else 6297 return list.get(i); 6298 } 6299 6300 6301 private List<String> listReferenceProfiles(ElementDefinition ed) { 6302 List<String> res = new ArrayList<String>(); 6303 for (TypeRefComponent tr : ed.getType()) { 6304 // code is null if we're dealing with "value" and profile is null if we just have Reference() 6305 if (tr.hasTarget() && tr.hasTargetProfile()) 6306 for (UriType u : tr.getTargetProfile()) 6307 res.add(u.getValue()); 6308 } 6309 return res; 6310 } 6311 6312 6313 private String nameForElement(ElementDefinition ed) { 6314 return ed.getPath().substring(ed.getPath().indexOf(".")+1); 6315 } 6316 6317 6318 private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException { 6319 SpanEntry res = new SpanEntry(); 6320 res.setName(name); 6321 res.setCardinality(cardinality); 6322 res.setProfileLink(profile.getUserString("path")); 6323 res.setResType(profile.getType()); 6324 StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType()); 6325 if (base != null) 6326 res.setResLink(base.getUserString("path")); 6327 res.setId(profile.getId()); 6328 res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT); 6329 StringBuilder b = new StringBuilder(); 6330 b.append(res.getResType()); 6331 boolean first = true; 6332 boolean open = false; 6333 if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 6334 res.setDescription(profile.getName()); 6335 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 6336 if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) { 6337 if (first) { 6338 open = true; 6339 first = false; 6340 b.append("["); 6341 } else { 6342 b.append(", "); 6343 } 6344 b.append(tail(ed.getBase().getPath())); 6345 b.append("="); 6346 b.append(summarize(ed.getFixed())); 6347 } 6348 } 6349 if (open) 6350 b.append("]"); 6351 } else 6352 res.setDescription("Base FHIR "+profile.getName()); 6353 res.setType(b.toString()); 6354 return res ; 6355 } 6356 6357 6358 private String summarize(DataType value) throws IOException { 6359 if (value instanceof Coding) 6360 return summarizeCoding((Coding) value); 6361 else if (value instanceof CodeableConcept) 6362 return summarizeCodeableConcept((CodeableConcept) value); 6363 else 6364 return buildJson(value); 6365 } 6366 6367 6368 private String summarizeCoding(Coding value) { 6369 String uri = value.getSystem(); 6370 String system = TerminologyRenderer.describeSystem(uri); 6371 if (Utilities.isURL(system)) { 6372 if (system.equals("http://cap.org/protocols")) 6373 system = "CAP Code"; 6374 } 6375 return system+" "+value.getCode(); 6376 } 6377 6378 6379 private String summarizeCodeableConcept(CodeableConcept value) { 6380 if (value.hasCoding()) 6381 return summarizeCoding(value.getCodingFirstRep()); 6382 else 6383 return value.getText(); 6384 } 6385 6386 6387 private boolean isKeyProperty(String path) { 6388 return Utilities.existsInList(path, "Observation.code"); 6389 } 6390 6391 6392 public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) { 6393 TableModel model = gen.new TableModel(id, true); 6394 6395 model.setDocoImg(prefix+"help16.png"); 6396 model.setDocoRef(Utilities.pathURL(prefix, "formats.html#table")); // todo: change to graph definition 6397 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0)); 6398 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Card.", "Minimum and Maximum # of times the the element can appear in the instance", null, 0)); 6399 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0)); 6400 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0)); 6401 return model; 6402 } 6403 6404 private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException { 6405 Row row = gen.new Row(); 6406 rows.add(row); 6407 row.setAnchor(span.getId()); 6408 //row.setColor(..?); 6409 if (span.isProfile()) { 6410 row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE); 6411 } else { 6412 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 6413 } 6414 6415 row.getCells().add(gen.new Cell(null, null, span.getName(), null, null)); 6416 row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null)); 6417 row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null)); 6418 row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null)); 6419 6420 for (SpanEntry child : span.getChildren()) { 6421 genSpanEntry(gen, row.getSubRows(), child); 6422 } 6423 } 6424 6425 6426 public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) { 6427 if (discriminator.endsWith("@pattern")) 6428 return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 6429 if (discriminator.endsWith("@profile")) 6430 return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 6431 if (discriminator.endsWith("@type")) 6432 return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6)); 6433 if (discriminator.endsWith("@exists")) 6434 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 6435 if (isExists) 6436 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 6437 return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator); 6438 } 6439 6440 6441 private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) { 6442 return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str); 6443 } 6444 6445 6446 public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException { 6447 switch (t.getType()) { 6448 case PROFILE: return t.getPath()+"/@profile"; 6449 case PATTERN: return t.getPath()+"/@pattern"; 6450 case TYPE: return t.getPath()+"/@type"; 6451 case VALUE: return t.getPath(); 6452 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 6453 default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2"); 6454 } 6455 } 6456 6457 6458 public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) { 6459 String epath = url.substring(54); 6460 if (!epath.contains(".")) 6461 return null; 6462 String type = epath.substring(0, epath.indexOf(".")); 6463 StructureDefinition sd = context.fetchTypeDefinition(type); 6464 if (sd == null) 6465 return null; 6466 ElementDefinition ed = null; 6467 for (ElementDefinition t : sd.getSnapshot().getElement()) { 6468 if (t.getPath().equals(epath)) { 6469 ed = t; 6470 break; 6471 } 6472 } 6473 if (ed == null) 6474 return null; 6475 if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) { 6476 return null; 6477 } else { 6478 StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"); 6479 StructureDefinition ext = template.copy(); 6480 ext.setUrl(url); 6481 ext.setId("extension-"+epath); 6482 ext.setName("Extension-"+epath); 6483 ext.setTitle("Extension for r4 "+epath); 6484 ext.setStatus(sd.getStatus()); 6485 ext.setDate(sd.getDate()); 6486 ext.getContact().clear(); 6487 ext.getContact().addAll(sd.getContact()); 6488 ext.setFhirVersion(sd.getFhirVersion()); 6489 ext.setDescription(ed.getDefinition()); 6490 ext.getContext().clear(); 6491 ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf("."))); 6492 ext.getDifferential().getElement().clear(); 6493 ext.getSnapshot().getElement().get(3).setFixed(new UriType(url)); 6494 ext.getSnapshot().getElement().set(4, ed.copy()); 6495 ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary())); 6496 return ext; 6497 } 6498 6499 } 6500 6501 6502 public boolean isThrowException() { 6503 return exception; 6504 } 6505 6506 6507 public void setThrowException(boolean exception) { 6508 this.exception = exception; 6509 } 6510 6511 6512 public ValidationOptions getTerminologyServiceOptions() { 6513 return terminologyServiceOptions; 6514 } 6515 6516 6517 public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) { 6518 this.terminologyServiceOptions = terminologyServiceOptions; 6519 } 6520 6521 6522 public boolean isNewSlicingProcessing() { 6523 return newSlicingProcessing; 6524 } 6525 6526 6527 public void setNewSlicingProcessing(boolean newSlicingProcessing) { 6528 this.newSlicingProcessing = newSlicingProcessing; 6529 } 6530 6531 6532 public boolean isDebug() { 6533 return debug; 6534 } 6535 6536 6537 public void setDebug(boolean debug) { 6538 this.debug = debug; 6539 } 6540 6541 6542 public String getDefWebRoot() { 6543 return defWebRoot; 6544 } 6545 6546 6547 public void setDefWebRoot(String defWebRoot) { 6548 this.defWebRoot = defWebRoot; 6549 if (!this.defWebRoot.endsWith("/")) 6550 this.defWebRoot = this.defWebRoot + '/'; 6551 } 6552 6553 6554 public static StructureDefinition makeBaseDefinition(FHIRVersion fhirVersion) { 6555 StructureDefinition base = new StructureDefinition(); 6556 base.setId("Base"); 6557 base.setUrl("http://hl7.org/fhir/StructureDefinition/Base"); 6558 base.setVersion(fhirVersion.toCode()); 6559 base.setName("Base"); 6560 base.setStatus(PublicationStatus.ACTIVE); 6561 base.setDate(new Date()); 6562 base.setFhirVersion(fhirVersion); 6563 base.setKind(StructureDefinitionKind.COMPLEXTYPE); 6564 base.setAbstract(true); 6565 base.setType("Base"); 6566 ElementDefinition e = base.getSnapshot().getElementFirstRep(); 6567 e.setId("Base"); 6568 e.setPath("Base"); 6569 e.setMin(0); 6570 e.setMax("*"); 6571 e.getBase().setPath("Base"); 6572 e.getBase().setMin(0); 6573 e.getBase().setMax("*"); 6574 e.setIsModifier(false); 6575 e = base.getDifferential().getElementFirstRep(); 6576 e.setId("Base"); 6577 e.setPath("Base"); 6578 e.setMin(0); 6579 e.setMax("*"); 6580 return base; 6581 } 6582 6583 public XVerExtensionManager getXver() { 6584 return xver; 6585 } 6586 6587 public ProfileUtilities setXver(XVerExtensionManager xver) { 6588 this.xver = xver; 6589 return this; 6590 } 6591 6592 6593 public List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) { 6594 List<ElementChoiceGroup> result = new ArrayList<>(); 6595 for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { 6596 ElementChoiceGroup grp = processConstraint(children, c); 6597 if (grp != null) { 6598 result.add(grp); 6599 } 6600 } 6601 return result; 6602 } 6603 6604 private ElementChoiceGroup processConstraint(List<ElementDefinition> children, ElementDefinitionConstraintComponent c) { 6605 if (!c.hasExpression()) { 6606 return null; 6607 } 6608 ExpressionNode expr = null; 6609 try { 6610 expr = fpe.parse(c.getExpression()); 6611 } catch (Exception e) { 6612 return null; 6613 } 6614 if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) { 6615 return null; 6616 } 6617 ExpressionNode n1 = expr.getGroup(); 6618 ExpressionNode n2 = expr.getOpNext(); 6619 if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) { 6620 return null; 6621 } 6622 ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals); 6623 while (n1 != null) { 6624 if (n1.getKind() != Kind.Name || n1.getInner() != null) { 6625 return null; 6626 } 6627 grp.elements.add(n1.getName()); 6628 if (n1.getOperation() == null || n1.getOperation() == Operation.Union) { 6629 n1 = n1.getOpNext(); 6630 } else { 6631 return null; 6632 } 6633 } 6634 int total = 0; 6635 for (String n : grp.elements) { 6636 boolean found = false; 6637 for (ElementDefinition child : children) { 6638 String name = tail(child.getPath()); 6639 if (n.equals(name)) { 6640 found = true; 6641 if (!"0".equals(child.getMax())) { 6642 total++; 6643 } 6644 } 6645 } 6646 if (!found) { 6647 return null; 6648 } 6649 } 6650 if (total <= 1) { 6651 return null; 6652 } 6653 return grp; 6654 } 6655 6656 public static boolean allTypesMustSupport(ElementDefinition e) { 6657 boolean all = true; 6658 boolean any = false; 6659 for (TypeRefComponent tr : e.getType()) { 6660 all = all && isMustSupport(tr); 6661 any = any || isMustSupport(tr); 6662 } 6663 return !all && !any; 6664 } 6665 6666 public static boolean allProfilesMustSupport(List<CanonicalType> profiles) { 6667 boolean all = true; 6668 boolean any = false; 6669 for (CanonicalType u : profiles) { 6670 all = all && isMustSupport(u); 6671 any = any || isMustSupport(u); 6672 } 6673 return !all && !any; 6674 } 6675 public static boolean isMustSupportDirect(TypeRefComponent tr) { 6676 return ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))); 6677 } 6678 6679 public static boolean isMustSupport(TypeRefComponent tr) { 6680 if ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))) { 6681 return true; 6682 } 6683 if (isMustSupport(tr.getProfile())) { 6684 return true; 6685 } 6686 return isMustSupport(tr.getTargetProfile()); 6687 } 6688 6689 public static boolean isMustSupport(List<CanonicalType> profiles) { 6690 for (CanonicalType ct : profiles) { 6691 if (isMustSupport(ct)) { 6692 return true; 6693 } 6694 } 6695 return false; 6696 } 6697 6698 6699 public static boolean isMustSupport(CanonicalType profile) { 6700 return "true".equals(ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_MUST_SUPPORT)); 6701 } 6702 6703 public ElementDefinitionResolution resolveContentRef(StructureDefinition structure, ElementDefinition element) { 6704 return getElementById(structure, structure.getSnapshot().getElement(), element.getContentReference()); 6705 } 6706 6707 public Set<String> getMasterSourceFileNames() { 6708 return masterSourceFileNames; 6709 } 6710 6711 public void setMasterSourceFileNames(Set<String> masterSourceFileNames) { 6712 this.masterSourceFileNames = masterSourceFileNames; 6713 } 6714 6715 6716}