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