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