001package org.hl7.fhir.r4.conformance; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032import java.io.IOException; 033import java.io.OutputStream; 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.Comparator; 037import java.util.HashMap; 038import java.util.HashSet; 039import java.util.Iterator; 040import java.util.List; 041import java.util.Map; 042import java.util.Set; 043 044import org.apache.commons.lang3.StringUtils; 045import org.hl7.fhir.exceptions.DefinitionException; 046import org.hl7.fhir.exceptions.FHIRException; 047import org.hl7.fhir.exceptions.FHIRFormatError; 048import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution; 049import org.hl7.fhir.r4.context.IWorkerContext; 050import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult; 051import org.hl7.fhir.r4.elementmodel.ObjectConverter; 052import org.hl7.fhir.r4.elementmodel.Property; 053import org.hl7.fhir.r4.formats.IParser; 054import org.hl7.fhir.r4.model.Base; 055import org.hl7.fhir.r4.model.BooleanType; 056import org.hl7.fhir.r4.model.CanonicalType; 057import org.hl7.fhir.r4.model.CodeType; 058import org.hl7.fhir.r4.model.CodeableConcept; 059import org.hl7.fhir.r4.model.Coding; 060import org.hl7.fhir.r4.model.Element; 061import org.hl7.fhir.r4.model.ElementDefinition; 062import org.hl7.fhir.r4.model.ElementDefinition.AggregationMode; 063import org.hl7.fhir.r4.model.ElementDefinition.DiscriminatorType; 064import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBaseComponent; 065import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 066import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent; 067import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionExampleComponent; 068import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent; 069import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent; 070import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 071import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation; 072import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules; 073import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 074import org.hl7.fhir.r4.model.Enumeration; 075import org.hl7.fhir.r4.model.Enumerations.BindingStrength; 076import org.hl7.fhir.r4.model.Extension; 077import org.hl7.fhir.r4.model.IntegerType; 078import org.hl7.fhir.r4.model.PrimitiveType; 079import org.hl7.fhir.r4.model.Quantity; 080import org.hl7.fhir.r4.model.Resource; 081import org.hl7.fhir.r4.model.StringType; 082import org.hl7.fhir.r4.model.StructureDefinition; 083import org.hl7.fhir.r4.model.StructureDefinition.ExtensionContextType; 084import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionContextComponent; 085import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionDifferentialComponent; 086import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 087import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent; 088import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent; 089import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 090import org.hl7.fhir.r4.model.Type; 091import org.hl7.fhir.r4.model.UriType; 092import org.hl7.fhir.r4.model.ValueSet; 093import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; 094import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 095import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 096import org.hl7.fhir.r4.utils.NarrativeGenerator; 097import org.hl7.fhir.r4.utils.ToolingExtensions; 098import org.hl7.fhir.r4.utils.TranslatingUtilities; 099import org.hl7.fhir.r4.utils.formats.CSVWriter; 100import org.hl7.fhir.r4.utils.formats.XLSXWriter; 101import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 102import org.hl7.fhir.utilities.FhirPublication; 103import org.hl7.fhir.utilities.TerminologyServiceOptions; 104import org.hl7.fhir.utilities.Utilities; 105import org.hl7.fhir.utilities.VersionUtilities; 106import org.hl7.fhir.utilities.i18n.RenderingI18nContext; 107import org.hl7.fhir.utilities.validation.ValidationMessage; 108import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 109import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 110import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 111import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 112import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 113import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMode; 114import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 115import org.hl7.fhir.utilities.xhtml.XhtmlNode; 116import org.hl7.fhir.utilities.xml.SchematronWriter; 117import org.hl7.fhir.utilities.xml.SchematronWriter.Rule; 118import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType; 119import org.hl7.fhir.utilities.xml.SchematronWriter.Section; 120 121/** 122 * This class provides a set of utility operations for working with Profiles. 123 * Key functionality: * getChildMap --? * getChildList * generateSnapshot: Given 124 * a base (snapshot) profile structure, and a differential profile, generate a 125 * new snapshot profile * closeDifferential: fill out a differential by 126 * excluding anything not mentioned * generateExtensionsTable: generate the HTML 127 * for a hierarchical table presentation of the extensions * generateTable: 128 * generate the HTML for a hierarchical table presentation of a structure * 129 * generateSpanningTable: generate the HTML for a table presentation of a 130 * network of structures, starting at a nominated point * summarize: describe 131 * the contents of a profile 132 * 133 * note to maintainers: Do not make modifications to the snapshot generation 134 * without first changing the snapshot generation test cases to demonstrate the 135 * grounds for your change 136 * 137 * @author Grahame 138 * 139 */ 140public class ProfileUtilities extends TranslatingUtilities { 141 142 public class ElementRedirection { 143 144 private String path; 145 private ElementDefinition element; 146 147 public ElementRedirection(ElementDefinition element, String path) { 148 this.path = path; 149 this.element = element; 150 } 151 152 public ElementDefinition getElement() { 153 return element; 154 } 155 156 @Override 157 public String toString() { 158 return element.toString() + " : " + path; 159 } 160 161 public String getPath() { 162 return path; 163 } 164 165 } 166 167 public class TypeSlice { 168 private ElementDefinition defn; 169 private String type; 170 171 public TypeSlice(ElementDefinition defn, String type) { 172 super(); 173 this.defn = defn; 174 this.type = type; 175 } 176 177 public ElementDefinition getDefn() { 178 return defn; 179 } 180 181 public String getType() { 182 return type; 183 } 184 185 } 186 187 private static final int MAX_RECURSION_LIMIT = 10; 188 189 public class ExtensionContext { 190 191 private ElementDefinition element; 192 private StructureDefinition defn; 193 194 public ExtensionContext(StructureDefinition ext, ElementDefinition ed) { 195 this.defn = ext; 196 this.element = ed; 197 } 198 199 public ElementDefinition getElement() { 200 return element; 201 } 202 203 public StructureDefinition getDefn() { 204 return defn; 205 } 206 207 public String getUrl() { 208 if (element == defn.getSnapshot().getElement().get(0)) 209 return defn.getUrl(); 210 else 211 return element.getSliceName(); 212 } 213 214 public ElementDefinition getExtensionValueDefinition() { 215 int i = defn.getSnapshot().getElement().indexOf(element) + 1; 216 while (i < defn.getSnapshot().getElement().size()) { 217 ElementDefinition ed = defn.getSnapshot().getElement().get(i); 218 if (ed.getPath().equals(element.getPath())) 219 return null; 220 if (ed.getPath().startsWith(element.getPath() + ".value")) 221 return ed; 222 i++; 223 } 224 return null; 225 } 226 } 227 228 private static final String ROW_COLOR_ERROR = "#ffcccc"; 229 private static final String ROW_COLOR_FATAL = "#ff9999"; 230 private static final String ROW_COLOR_WARNING = "#ffebcc"; 231 private static final String ROW_COLOR_HINT = "#ebf5ff"; 232 private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8"; 233 public static final int STATUS_OK = 0; 234 public static final int STATUS_HINT = 1; 235 public static final int STATUS_WARNING = 2; 236 public static final int STATUS_ERROR = 3; 237 public static final int STATUS_FATAL = 4; 238 239 private static final String DERIVATION_EQUALS = "derivation.equals"; 240 public static final String DERIVATION_POINTER = "derived.pointer"; 241 public static final String IS_DERIVED = "derived.fact"; 242 public static final String UD_ERROR_STATUS = "error-status"; 243 private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed"; 244 private final boolean ADD_REFERENCE_TO_TABLE = true; 245 246 private boolean useTableForFixedValues = true; 247 private boolean debug; 248 249 // note that ProfileUtilities are used re-entrantly internally, so nothing with 250 // process state can be here 251 private final IWorkerContext context; 252 private List<ValidationMessage> messages; 253 private List<String> snapshotStack = new ArrayList<String>(); 254 private ProfileKnowledgeProvider pkp; 255 private boolean igmode; 256 private boolean exception; 257 private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions(FhirPublication.R4); 258 private boolean newSlicingProcessing; 259 260 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) { 261 super(); 262 this.context = context; 263 this.messages = messages; 264 this.pkp = pkp; 265 } 266 267 private class UnusedTracker { 268 private boolean used; 269 } 270 271 public boolean isIgmode() { 272 return igmode; 273 } 274 275 public void setIgmode(boolean igmode) { 276 this.igmode = igmode; 277 } 278 279 public interface ProfileKnowledgeProvider { 280 public class BindingResolution { 281 public String display; 282 public String url; 283 } 284 285 public boolean isDatatype(String typeSimple); 286 287 public boolean isResource(String typeSimple); 288 289 public boolean hasLinkFor(String typeSimple); 290 291 public String getLinkFor(String corePath, String typeSimple); 292 293 public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, 294 String path) throws FHIRException; 295 296 public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException; 297 298 public String getLinkForProfile(StructureDefinition profile, String url); 299 300 public boolean prependLinks(); 301 302 public String getLinkForUrl(String corePath, String s); 303 } 304 305 public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) 306 throws DefinitionException { 307 if (element.getContentReference() != null) { 308 for (ElementDefinition e : profile.getSnapshot().getElement()) { 309 if (element.getContentReference().equals("#" + e.getId())) 310 return getChildMap(profile, e); 311 } 312 throw new DefinitionException( 313 "Unable to resolve name reference " + element.getContentReference() + " at path " + element.getPath()); 314 315 } else { 316 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 317 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 318 String path = element.getPath(); 319 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 320 ElementDefinition e = elements.get(index); 321 if (e.getPath().startsWith(path + ".")) { 322 // We only want direct children, not all descendants 323 if (!e.getPath().substring(path.length() + 1).contains(".")) 324 res.add(e); 325 } else 326 break; 327 } 328 return res; 329 } 330 } 331 332 public static List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) 333 throws DefinitionException { 334 if (!element.hasSlicing()) 335 throw new Error("getSliceList should only be called when the element has slicing"); 336 337 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 338 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 339 String path = element.getPath(); 340 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 341 ElementDefinition e = elements.get(index); 342 if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) { 343 // We want elements with the same path (until we hit an element that doesn't 344 // start with the same path) 345 if (e.getPath().equals(element.getPath())) 346 res.add(e); 347 } else 348 break; 349 } 350 return res; 351 } 352 353 /** 354 * Given a Structure, navigate to the element given by the path and return the 355 * direct children of that element 356 * 357 * @param structure The structure to navigate into 358 * @param path The path of the element within the structure to get the 359 * children for 360 * @return A List containing the element children (all of them are Elements) 361 */ 362 public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) { 363 return getChildList(profile, path, id, false); 364 } 365 366 public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, 367 boolean diff) { 368 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 369 370 boolean capturing = id == null; 371 if (id == null && !path.contains(".")) 372 capturing = true; 373 374 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 375 for (ElementDefinition e : list) { 376 if (e == null) 377 throw new Error("element = null: " + profile.getUrl()); 378 if (e.getId() == null) 379 throw new Error("element id = null: " + e.toString() + " on " + profile.getUrl()); 380 381 if (!capturing && id != null && e.getId().equals(id)) { 382 capturing = true; 383 } 384 385 // If our element is a slice, stop capturing children as soon as we see the next 386 // slice 387 if (capturing && e.hasId() && id != null && !e.getId().equals(id) && e.getPath().equals(path)) 388 break; 389 390 if (capturing) { 391 String p = e.getPath(); 392 393 if (!Utilities.noString(e.getContentReference()) && path.startsWith(p)) { 394 if (path.length() > p.length()) 395 return getChildList(profile, e.getContentReference() + "." + path.substring(p.length() + 1), null, diff); 396 else 397 return getChildList(profile, e.getContentReference(), null, diff); 398 399 } else if (p.startsWith(path + ".") && !p.equals(path)) { 400 String tail = p.substring(path.length() + 1); 401 if (!tail.contains(".")) { 402 res.add(e); 403 } 404 } 405 } 406 } 407 408 return res; 409 } 410 411 public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, 412 boolean diff) { 413 return getChildList(structure, element.getPath(), element.getId(), diff); 414 } 415 416 public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) { 417 return getChildList(structure, element.getPath(), element.getId(), false); 418 } 419 420 public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException { 421 if (base == null) 422 throw new DefinitionException("no base profile provided"); 423 if (derived == null) 424 throw new DefinitionException("no derived structure provided"); 425 426 for (StructureDefinitionMappingComponent baseMap : base.getMapping()) { 427 boolean found = false; 428 for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) { 429 if (derivedMap.getUri() != null && derivedMap.getUri().equals(baseMap.getUri())) { 430 found = true; 431 break; 432 } 433 } 434 if (!found) 435 derived.getMapping().add(baseMap); 436 } 437 } 438 439 /** 440 * Given a base (snapshot) profile structure, and a differential profile, 441 * generate a new snapshot profile 442 * 443 * @param base - the base structure on which the differential will 444 * be applied 445 * @param differential - the differential to apply to the base 446 * @param url - where the base has relative urls for profile 447 * references, these need to be converted to absolutes 448 * by prepending this URL (e.g. the canonical URL) 449 * @param webUrl - where the base has relative urls in markdown, these 450 * need to be converted to absolutes by prepending this 451 * URL (this is not the same as the canonical URL) 452 * @param trimDifferential - if this is true, then the snap short generator will 453 * remove any material in the element definitions that 454 * is not different to the base 455 * @return 456 * @throws FHIRException 457 * @throws DefinitionException 458 * @throws Exception 459 */ 460 public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, 461 String profileName) throws DefinitionException, FHIRException { 462 if (base == null) 463 throw new DefinitionException("no base profile provided"); 464 if (derived == null) 465 throw new DefinitionException("no derived structure provided"); 466 467 if (snapshotStack.contains(derived.getUrl())) 468 throw new DefinitionException( 469 "Circular snapshot references detected; cannot generate snapshot (stack = " + snapshotStack.toString() + ")"); 470 snapshotStack.add(derived.getUrl()); 471 472 if (!Utilities.noString(webUrl) && !webUrl.endsWith("/")) 473 webUrl = webUrl + '/'; 474 475 derived.setSnapshot(new StructureDefinitionSnapshotComponent()); 476 477 try { 478 // so we have two lists - the base list, and the differential list 479 // the differential list is only allowed to include things that are in the base 480 // list, but 481 // is allowed to include them multiple times - thereby slicing them 482 483 // our approach is to walk through the base list, and see whether the 484 // differential 485 // says anything about them. 486 int baseCursor = 0; 487 int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by 488 // longer paths 489 490 if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") 491 && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) 492 throw new Error("type on first differential element!"); 493 494 for (ElementDefinition e : derived.getDifferential().getElement()) 495 e.clearUserData(GENERATED_IN_SNAPSHOT); 496 497 // we actually delegate the work to a subroutine so we can re-enter it with a 498 // different cursors 499 StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential()); // we make a copy here 500 // because we're sometimes 501 // going to hack the 502 // differential while 503 // processing it. Have to 504 // migrate user data back 505 // afterwards 506 507 processPaths("", derived.getSnapshot(), base.getSnapshot(), diff, baseCursor, diffCursor, 508 base.getSnapshot().getElement().size() - 1, 509 derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size() - 1 : -1, url, webUrl, 510 derived.present(), null, null, false, base.getUrl(), null, false, new ArrayList<ElementRedirection>(), base); 511 if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty()) 512 throw new Error("type on first snapshot element for " + derived.getSnapshot().getElementFirstRep().getPath() 513 + " in " + derived.getUrl() + " from " + base.getUrl()); 514 updateMaps(base, derived); 515 516 if (debug) { 517 System.out.println("Differential: "); 518 for (ElementDefinition ed : derived.getDifferential().getElement()) 519 System.out.println(" " + ed.getPath() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." 520 + ed.getMax() + "]" + sliceSummary(ed) + " id = " + ed.getId() + " " + constraintSummary(ed)); 521 System.out.println("Snapshot: "); 522 for (ElementDefinition ed : derived.getSnapshot().getElement()) 523 System.out.println(" " + ed.getPath() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".." 524 + ed.getMax() + "]" + sliceSummary(ed) + " id = " + ed.getId() + " " + constraintSummary(ed)); 525 } 526 setIds(derived, false); 527 // Check that all differential elements have a corresponding snapshot element 528 for (ElementDefinition e : diff.getElement()) { 529 if (!e.hasUserData("diff-source")) 530 throw new Error("Unxpected internal condition - no source on diff element"); 531 else { 532 if (e.hasUserData(DERIVATION_EQUALS)) 533 ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_EQUALS, e.getUserData(DERIVATION_EQUALS)); 534 if (e.hasUserData(DERIVATION_POINTER)) 535 ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_POINTER, e.getUserData(DERIVATION_POINTER)); 536 } 537 if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) { 538 System.out.println("Error in snapshot generation: Differential for " + derived.getUrl() + " with " 539 + (e.hasId() ? "id: " + e.getId() : "path: " + e.getPath()) 540 + " has an element that is not marked with a snapshot match"); 541 if (exception) 542 throw new DefinitionException("Snapshot for " + derived.getUrl() 543 + " does not contain an element that matches an existing differential element that has " 544 + (e.hasId() ? "id: " + e.getId() : "path: " + e.getPath())); 545 else 546 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, 547 "Snapshot for " + derived.getUrl() 548 + " does not contain an element that matches an existing differential element that has id: " 549 + e.getId(), 550 ValidationMessage.IssueSeverity.ERROR)); 551 } 552 } 553 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 554 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 555 if (!ed.hasBase()) { 556 ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax()); 557 } 558 } 559 } 560 } catch (Exception e) { 561 // if we had an exception generating the snapshot, make sure we don't leave any 562 // half generated snapshot behind 563 derived.setSnapshot(null); 564 throw e; 565 } 566 } 567 568 private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) { 569 StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent(); 570 for (ElementDefinition sed : source.getElement()) { 571 ElementDefinition ted = sed.copy(); 572 diff.getElement().add(ted); 573 ted.setUserData("diff-source", sed); 574 } 575 return diff; 576 } 577 578 private String constraintSummary(ElementDefinition ed) { 579 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 580 if (ed.hasPattern()) 581 b.append("pattern=" + ed.getPattern().fhirType()); 582 if (ed.hasFixed()) 583 b.append("fixed=" + ed.getFixed().fhirType()); 584 if (ed.hasConstraint()) 585 b.append("constraints=" + ed.getConstraint().size()); 586 return b.toString(); 587 } 588 589 private String sliceSummary(ElementDefinition ed) { 590 if (!ed.hasSlicing() && !ed.hasSliceName()) 591 return ""; 592 if (ed.hasSliceName()) 593 return " (slicename = " + ed.getSliceName() + ")"; 594 595 StringBuilder b = new StringBuilder(); 596 boolean first = true; 597 for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) { 598 if (first) 599 first = false; 600 else 601 b.append("|"); 602 b.append(d.getPath()); 603 } 604 return " (slicing by " + b.toString() + ")"; 605 } 606 607 private String typeSummary(ElementDefinition ed) { 608 StringBuilder b = new StringBuilder(); 609 boolean first = true; 610 for (TypeRefComponent tr : ed.getType()) { 611 if (first) 612 first = false; 613 else 614 b.append("|"); 615 b.append(tr.getWorkingCode()); 616 } 617 return b.toString(); 618 } 619 620 private String typeSummaryWithProfile(ElementDefinition ed) { 621 StringBuilder b = new StringBuilder(); 622 boolean first = true; 623 for (TypeRefComponent tr : ed.getType()) { 624 if (first) 625 first = false; 626 else 627 b.append("|"); 628 b.append(tr.getWorkingCode()); 629 if (tr.hasProfile()) { 630 b.append("("); 631 b.append(tr.getProfile()); 632 b.append(")"); 633 634 } 635 } 636 return b.toString(); 637 } 638 639 private boolean findMatchingElement(String id, List<ElementDefinition> list) { 640 for (ElementDefinition ed : list) { 641 if (ed.getId().equals(id)) 642 return true; 643 if (id.endsWith("[x]")) { 644 if (ed.getId().startsWith(id.substring(0, id.length() - 3)) 645 && !ed.getId().substring(id.length() - 3).contains(".")) 646 return true; 647 } 648 } 649 return false; 650 } 651 652 /** 653 * @param trimDifferential 654 * @param srcSD 655 * @throws DefinitionException, FHIRException 656 * @throws Exception 657 */ 658 private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, 659 StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, 660 int diffCursor, int baseLimit, int diffLimit, String url, String webUrl, String profileName, 661 String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, 662 boolean slicingDone, List<ElementRedirection> redirector, StructureDefinition srcSD) 663 throws DefinitionException, FHIRException { 664 if (debug) 665 System.out.println(indent + "PP @ " + resultPathBase + " / " + contextPathSrc + " : base = " + baseCursor + " to " 666 + baseLimit + ", diff = " + diffCursor + " to " + diffLimit + " (slicing = " + slicingDone + ", redirector = " 667 + (redirector == null ? "null" : redirector.toString()) + ")"); 668 ElementDefinition res = null; 669 List<TypeSlice> typeList = new ArrayList<>(); 670 // just repeat processing entries until we run out of our allowed scope (1st 671 // entry, the allowed scope is all the entries) 672 while (baseCursor <= baseLimit) { 673 // get the current focus of the base, and decide what to do 674 ElementDefinition currentBase = base.getElement().get(baseCursor); 675 String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector); 676 if (debug) 677 System.out.println(indent + " - " + cpath + ": base = " + baseCursor + " (" 678 + descED(base.getElement(), baseCursor) + ") to " + baseLimit + " (" + descED(base.getElement(), baseLimit) 679 + "), diff = " + diffCursor + " (" + descED(differential.getElement(), diffCursor) + ") to " + diffLimit 680 + " (" + descED(differential.getElement(), diffLimit) + ") " + "(slicingDone = " + slicingDone 681 + ") (diffpath= " 682 + (differential.getElement().size() > diffCursor ? differential.getElement().get(diffCursor).getPath() 683 : "n/a") 684 + ")"); 685 List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get 686 // a 687 // list 688 // of 689 // matching 690 // elements 691 // in 692 // scope 693 694 // in the simple case, source is not sliced. 695 if (!currentBase.hasSlicing()) { 696 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 697 // so we just copy it in 698 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 699 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 700 updateFromBase(outcome, currentBase); 701 markDerived(outcome); 702 if (resultPathBase == null) 703 resultPathBase = outcome.getPath(); 704 else if (!outcome.getPath().startsWith(resultPathBase)) 705 throw new DefinitionException("Adding wrong path"); 706 result.getElement().add(outcome); 707 if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), true)) { 708 // well, the profile walks into this, so we need to as well 709 // did we implicitly step into a new type? 710 if (baseHasChildren(base, currentBase)) { // not a new type here 711 processPaths(indent + " ", result, base, differential, baseCursor + 1, diffCursor, baseLimit, diffLimit, 712 url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, 713 resultPathBase, false, redirector, srcSD); 714 baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor + 1, baseLimit); 715 } else { 716 if (outcome.getType().size() == 0) { 717 throw new DefinitionException(diffMatches.get(0).getPath() + " has no children (" 718 + differential.getElement().get(diffCursor).getPath() + ") and no types in profile " + profileName); 719 } 720 if (outcome.getType().size() > 1) { 721 for (TypeRefComponent t : outcome.getType()) { 722 if (!t.getWorkingCode().equals("Reference")) 723 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 724 + differential.getElement().get(diffCursor).getPath() + ") and multiple types (" 725 + typeCode(outcome.getType()) + ") in profile " + profileName); 726 } 727 } 728 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 729 if (dt == null) 730 throw new DefinitionException( 731 "Unknown type " + outcome.getType().get(0) + " at " + diffMatches.get(0).getPath()); 732 contextName = dt.getUrl(); 733 int start = diffCursor; 734 while (differential.getElement().size() > diffCursor 735 && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath + ".")) 736 diffCursor++; 737 processPaths(indent + " ", result, dt.getSnapshot(), differential, 738 1 /* starting again on the data type, but skip the root */, start, 739 dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, webUrl, profileName, cpath, 740 outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD); 741 } 742 } 743 baseCursor++; 744 } else if (diffMatches.size() == 1 745 && (slicingDone || (!isImplicitSlicing(diffMatches.get(0), cpath) && !(diffMatches.get(0).hasSlicing() 746 || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName()))))) {// one matching element 747 // in the differential 748 ElementDefinition template = null; 749 if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 750 && diffMatches.get(0).getType().get(0).hasProfile() 751 && !"Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode())) { 752 CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0); 753 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue()); 754 if (sd != null) { 755 if (!sd.hasSnapshot()) { 756 StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 757 if (sdb == null) 758 throw new DefinitionException("no base for " + sd.getBaseDefinition()); 759 generateSnapshot(sdb, sd, sd.getUrl(), 760 (sdb.hasUserData("path")) ? Utilities.extractBaseUrl(sdb.getUserString("path")) : webUrl, 761 sd.getName()); 762 } 763 ElementDefinition src; 764 if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) { 765 src = null; 766 String eid = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT); 767 for (ElementDefinition t : sd.getSnapshot().getElement()) { 768 if (eid.equals(t.getId())) 769 src = t; 770 } 771 if (src == null) 772 throw new DefinitionException("Unable to find element " + eid + " in " + p.getValue()); 773 } else 774 src = sd.getSnapshot().getElement().get(0); 775 template = src.copy().setPath(currentBase.getPath()); 776 template.setSliceName(null); 777 // temporary work around 778 if (!"Extension".equals(diffMatches.get(0).getType().get(0).getCode())) { 779 template.setMin(currentBase.getMin()); 780 template.setMax(currentBase.getMax()); 781 } 782 } 783 } 784 if (template == null) 785 template = currentBase.copy(); 786 else 787 // some of what's in currentBase overrides template 788 template = overWriteWithCurrent(template, currentBase); 789 790 ElementDefinition outcome = updateURLs(url, webUrl, template); 791 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 792 if (res == null) 793 res = outcome; 794 updateFromBase(outcome, currentBase); 795 if (diffMatches.get(0).hasSliceName()) 796 outcome.setSliceName(diffMatches.get(0).getSliceName()); 797 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD); 798 removeStatusExtensions(outcome); 799// 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 800// outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode())); 801 outcome.setSlicing(null); 802 if (resultPathBase == null) 803 resultPathBase = outcome.getPath(); 804 else if (!outcome.getPath().startsWith(resultPathBase)) 805 throw new DefinitionException("Adding wrong path"); 806 result.getElement().add(outcome); 807 baseCursor++; 808 diffCursor = differential.getElement().indexOf(diffMatches.get(0)) + 1; 809 if (differential.getElement().size() > diffCursor && outcome.getPath().contains(".") 810 && (isDataType(outcome.getType()) || outcome.hasContentReference())) { // don't want to do this for the 811 // root, since that's base, and 812 // we're already processing it 813 if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".") 814 && !baseWalksInto(base.getElement(), baseCursor)) { 815 if (outcome.getType().size() > 1) { 816 if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) { 817 String en = tail(outcome.getPath()); 818 String tn = tail(diffMatches.get(0).getPath()); 819 String t = tn.substring(en.length() - 3); 820 if (isPrimitive(Utilities.uncapitalize(t))) 821 t = Utilities.uncapitalize(t); 822 List<TypeRefComponent> ntr = getByTypeName(outcome.getType(), t); // keep any additional information 823 if (ntr.isEmpty()) 824 ntr.add(new TypeRefComponent().setCode(t)); 825 outcome.getType().clear(); 826 outcome.getType().addAll(ntr); 827 } 828 if (outcome.getType().size() > 1) 829 for (TypeRefComponent t : outcome.getType()) { 830 if (!t.getCode().equals("Reference")) { 831 boolean nonExtension = false; 832 for (ElementDefinition ed : diffMatches) 833 if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension")) 834 nonExtension = true; 835 if (nonExtension) 836 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 837 + differential.getElement().get(diffCursor).getPath() + ") and multiple types (" 838 + typeCode(outcome.getType()) + ") in profile " + profileName); 839 } 840 } 841 } 842 int start = diffCursor; 843 while (differential.getElement().size() > diffCursor && pathStartsWith( 844 differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) 845 diffCursor++; 846 if (outcome.hasContentReference()) { 847 ElementDefinition tgt = getElementById(base.getElement(), outcome.getContentReference()); 848 if (tgt == null) 849 throw new DefinitionException("Unable to resolve reference to " + outcome.getContentReference()); 850 replaceFromContentReference(outcome, tgt); 851 int nbc = base.getElement().indexOf(tgt) + 1; 852 int nbl = nbc; 853 while (nbl < base.getElement().size() 854 && base.getElement().get(nbl).getPath().startsWith(tgt.getPath() + ".")) 855 nbl++; 856 processPaths(indent + " ", result, base, differential, nbc, start - 1, nbl - 1, diffCursor - 1, url, 857 webUrl, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, 858 resultPathBase, false, redirectorStack(redirector, outcome, cpath), srcSD); 859 } else { 860 StructureDefinition dt = outcome.getType().size() == 1 ? getProfileForDataType(outcome.getType().get(0)) 861 : getProfileForDataType("Element"); 862 if (dt == null) 863 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 864 + differential.getElement().get(diffCursor).getPath() + ") for type " 865 + typeCode(outcome.getType()) + " in profile " + profileName + ", but can't find type"); 866 contextName = dt.getUrl(); 867 processPaths(indent + " ", result, dt.getSnapshot(), differential, 868 1 /* starting again on the data type, but skip the root */, start, 869 dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, webUrl, 870 profileName + pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), 871 trimDifferential, contextName, resultPathBase, false, new ArrayList<ElementRedirection>(), srcSD); 872 } 873 } 874 } 875 } else if (diffsConstrainTypes(diffMatches, cpath, typeList)) { 876 int start = 0; 877 int nbl = findEndOfElement(base, baseCursor); 878 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 879 ElementDefinition elementToRemove = null; 880 // we come here whether they are sliced in the diff, or whether the short cut is 881 // used. 882 if (typeList.get(0).type != null) { 883 // this is the short cut method, we've just dived in and specified a type slice. 884 // in R3 (and unpatched R4, as a workaround right now... 885 if (!VersionUtilities.isR4Plus(context.getVersion()) || !newSlicingProcessing) { // newSlicingProcessing is 886 // a work around for 887 // editorial loop 888 // dependency 889 // we insert a cloned element with the right types at the start of the 890 // diffMatches 891 ElementDefinition ed = new ElementDefinition(); 892 ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath)); 893 for (TypeSlice ts : typeList) 894 ed.addType().setCode(ts.type); 895 ed.setSlicing(new ElementDefinitionSlicingComponent()); 896 ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 897 ed.getSlicing().setRules(SlicingRules.CLOSED); 898 ed.getSlicing().setOrdered(false); 899 diffMatches.add(0, ed); 900 differential.getElement().add(ndc, ed); 901 elementToRemove = ed; 902 } else { 903 // as of R4, this changed; if there's no slice, there's no constraint on the 904 // slice types, only one the type. 905 // so the element we insert specifies no types (= all types) allowed in the 906 // base, not just the listed type. 907 // see also discussion here: 908 // https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element 909 ElementDefinition ed = new ElementDefinition(); 910 ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath)); 911 ed.setSlicing(new ElementDefinitionSlicingComponent()); 912 ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 913 ed.getSlicing().setRules(SlicingRules.CLOSED); 914 ed.getSlicing().setOrdered(false); 915 diffMatches.add(0, ed); 916 differential.getElement().add(ndc, ed); 917 elementToRemove = ed; 918 } 919 } 920 int ndl = findEndOfElement(differential, ndc); 921 // the first element is setting up the slicing 922 if (diffMatches.get(0).getSlicing().hasRules()) 923 if (diffMatches.get(0).getSlicing().getRules() != SlicingRules.CLOSED) 924 throw new FHIRException( 925 "Error at path " + contextPathSrc + ": Type slicing with slicing.rules != closed"); 926 if (diffMatches.get(0).getSlicing().hasOrdered()) 927 if (diffMatches.get(0).getSlicing().getOrdered()) 928 throw new FHIRException("Error at path " + contextPathSrc + ": Type slicing with slicing.ordered = true"); 929 if (diffMatches.get(0).getSlicing().hasDiscriminator()) { 930 if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1) 931 throw new FHIRException( 932 "Error at path " + contextPathSrc + ": Type slicing with slicing.discriminator.count() > 1"); 933 if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath())) 934 throw new FHIRException( 935 "Error at path " + contextPathSrc + ": Type slicing with slicing.discriminator.path != '$this'"); 936 if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE) 937 throw new FHIRException( 938 "Error at path " + contextPathSrc + ": Type slicing with slicing.discriminator.type != 'type'"); 939 } 940 // check the slice names too while we're at it... 941 for (TypeSlice ts : typeList) 942 if (ts.type != null) { 943 String tn = rootName(cpath) + Utilities.capitalize(ts.type); 944 if (!ts.defn.hasSliceName()) 945 ts.defn.setSliceName(tn); 946 else if (!ts.defn.getSliceName().equals(tn)) 947 throw new FHIRException( 948 "Error at path " + (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath) 949 + ": Slice name must be '" + tn + "' but is '" + ts.defn.getSliceName() + "'"); 950 if (!ts.defn.hasType()) 951 ts.defn.addType().setCode(ts.type); 952 else if (ts.defn.getType().size() > 1) 953 throw new FHIRException( 954 "Error at path " + (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath) 955 + ": Slice for type '" + tn + "' has more than one type '" + ts.defn.typeSummary() + "'"); 956 else if (!ts.defn.getType().get(0).getCode().equals(ts.type)) 957 throw new FHIRException( 958 "Error at path " + (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath) 959 + ": Slice for type '" + tn + "' has wrong type '" + ts.defn.typeSummary() + "'"); 960 } 961 962 // ok passed the checks. 963 // copy the root diff, and then process any children it has 964 ElementDefinition e = processPaths(indent + " ", result, base, differential, baseCursor, ndc, nbl, ndl, url, 965 webUrl, profileName + pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, 966 contextName, resultPathBase, true, redirector, srcSD); 967 if (e == null) 968 throw new FHIRException("Did not find type root: " + diffMatches.get(0).getPath()); 969 // now set up slicing on the e (cause it was wiped by what we called. 970 e.setSlicing(new ElementDefinitionSlicingComponent()); 971 e.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 972 e.getSlicing().setRules(SlicingRules.CLOSED); 973 e.getSlicing().setOrdered(false); 974 start++; 975 // now process the siblings, which should each be type constrained - and may 976 // also have their own children 977 // now we process the base scope repeatedly for each instance of the item in the 978 // differential list 979 for (int i = start; i < diffMatches.size(); i++) { 980 // our processing scope for the differential is the item in the list, and all 981 // the items before the next one in the list 982 ndc = differential.getElement().indexOf(diffMatches.get(i)); 983 ndl = findEndOfElement(differential, ndc); 984 processPaths(indent + " ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, 985 profileName + pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, 986 resultPathBase, true, redirector, srcSD); 987 } 988 if (elementToRemove != null) { 989 differential.getElement().remove(elementToRemove); 990 ndl--; 991 } 992 993 // ok, done with that - next in the base list 994 baseCursor = nbl + 1; 995 diffCursor = ndl + 1; 996 997 } else { 998 // ok, the differential slices the item. Let's check our pre-conditions to 999 // ensure that this is correct 1000 if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0))) 1001 // you can only slice an element that doesn't repeat if the sum total of your 1002 // slices is limited to 1 1003 // (but you might do that in order to split up constraints by type) 1004 throw new DefinitionException("Attempt to a slice an element that does not repeat: " + currentBase.getPath() 1005 + "/" + currentBase.getPath() + " from " + contextName + " in " + url); 1006 if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but 1007 // hasn't defined it. this is an error 1008 throw new DefinitionException("Differential does not have a slice: " + currentBase.getPath() + "/ (b:" 1009 + baseCursor + " of " + baseLimit + " / " + diffCursor + "/ " + diffLimit + ") in profile " + url); 1010 1011 // well, if it passed those preconditions then we slice the dest. 1012 int start = 0; 1013 int nbl = findEndOfElement(base, baseCursor); 1014// if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) { 1015 if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && (nbl > baseCursor || differential 1016 .getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0)) + 1)) { // there's 1017 // a 1018 // default 1019 // set 1020 // before 1021 // the 1022 // slices 1023 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 1024 int ndl = findEndOfElement(differential, ndc); 1025 ElementDefinition e = processPaths(indent + " ", result, base, differential, baseCursor, ndc, nbl, ndl, 1026 url, webUrl, profileName + pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, 1027 contextName, resultPathBase, true, redirector, srcSD); 1028 if (e == null) 1029 throw new FHIRException("Did not find single slice: " + diffMatches.get(0).getPath()); 1030 e.setSlicing(diffMatches.get(0).getSlicing()); 1031 start++; 1032 } else { 1033 // we're just going to accept the differential slicing at face value 1034 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 1035 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1036 updateFromBase(outcome, currentBase); 1037 1038 if (!diffMatches.get(0).hasSlicing()) 1039 outcome.setSlicing(makeExtensionSlicing()); 1040 else 1041 outcome.setSlicing(diffMatches.get(0).getSlicing().copy()); 1042 if (!outcome.getPath().startsWith(resultPathBase)) 1043 throw new DefinitionException("Adding wrong path"); 1044 result.getElement().add(outcome); 1045 1046 // differential - if the first one in the list has a name, we'll process it. 1047 // Else we'll treat it as the base definition of the slice. 1048 if (!diffMatches.get(0).hasSliceName()) { 1049 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD); 1050 removeStatusExtensions(outcome); 1051 if (!outcome.hasContentReference() && !outcome.hasType()) { 1052 throw new DefinitionException("not done yet"); 1053 } 1054 start++; 1055 // result.getElement().remove(result.getElement().size()-1); 1056 } else 1057 checkExtensionDoco(outcome); 1058 } 1059 // now, for each entry in the diff matches, we're going to process the base item 1060 // our processing scope for base is all the children of the current path 1061 int ndc = diffCursor; 1062 int ndl = diffCursor; 1063 for (int i = start; i < diffMatches.size(); i++) { 1064 // our processing scope for the differential is the item in the list, and all 1065 // the items before the next one in the list 1066 ndc = differential.getElement().indexOf(diffMatches.get(i)); 1067 ndl = findEndOfElement(differential, ndc); 1068 /* 1069 * if (skipSlicingElement && i == 0) { ndc = ndc + 1; if (ndc > ndl) continue; } 1070 */ 1071 // now we process the base scope repeatedly for each instance of the item in the 1072 // differential list 1073 processPaths(indent + " ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, 1074 profileName + pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, 1075 resultPathBase, true, redirector, srcSD); 1076 } 1077 // ok, done with that - next in the base list 1078 baseCursor = nbl + 1; 1079 diffCursor = ndl + 1; 1080 } 1081 } else { 1082 // the item is already sliced in the base profile. 1083 // here's the rules 1084 // 1. irrespective of whether the slicing is ordered or not, the definition 1085 // order must be maintained 1086 // 2. slice element names have to match. 1087 // 3. new slices must be introduced at the end 1088 // corallory: you can't re-slice existing slices. is that ok? 1089 1090 // we're going to need this: 1091 String path = currentBase.getPath(); 1092 ElementDefinition original = currentBase; 1093 1094 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 1095 // copy across the currentbase, and all of its children and siblings 1096 while (baseCursor < base.getElement().size() 1097 && base.getElement().get(baseCursor).getPath().startsWith(path)) { 1098 ElementDefinition outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy()); 1099 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1100 if (!outcome.getPath().startsWith(resultPathBase)) 1101 throw new DefinitionException( 1102 "Adding wrong path in profile " + profileName + ": " + outcome.getPath() + " vs " + resultPathBase); 1103 result.getElement().add(outcome); // so we just copy it in 1104 baseCursor++; 1105 } 1106 } else { 1107 // first - check that the slicing is ok 1108 boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED; 1109 int diffpos = 0; 1110 boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension"); 1111 if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything 1112 // about slicing 1113// if (!isExtension) 1114// diffpos++; // if there's a slice on the first, we'll ignore any content it has 1115 ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing(); 1116 ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing(); 1117 if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() 1118 && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement())) 1119 throw new DefinitionException( 1120 "Slicing rules on differential (" + summarizeSlicing(dSlice) + ") do not match those on base (" 1121 + summarizeSlicing(bSlice) + ") - order @ " + path + " (" + contextName + ")"); 1122 if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator())) 1123 throw new DefinitionException( 1124 "Slicing rules on differential (" + summarizeSlicing(dSlice) + ") do not match those on base (" 1125 + summarizeSlicing(bSlice) + ") - discriminator @ " + path + " (" + contextName + ")"); 1126 if (!ruleMatches(dSlice.getRules(), bSlice.getRules())) 1127 throw new DefinitionException( 1128 "Slicing rules on differential (" + summarizeSlicing(dSlice) + ") do not match those on base (" 1129 + summarizeSlicing(bSlice) + ") - rule @ " + path + " (" + contextName + ")"); 1130 } 1131 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 1132 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1133 updateFromBase(outcome, currentBase); 1134 if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) { 1135 updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing()); 1136 updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url, srcSD); // if there's no slice, 1137 // we don't want to 1138 // update the unsliced 1139 // description 1140 removeStatusExtensions(outcome); 1141 } else if (!diffMatches.get(0).hasSliceName()) 1142 diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't 1143 // called 1144 1145 result.getElement().add(outcome); 1146 1147 if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice 1148 diffpos++; 1149 } 1150 if (hasInnerDiffMatches(differential, cpath, diffpos, diffLimit, base.getElement(), false)) { 1151 int nbl = findEndOfElement(base, baseCursor); 1152 int ndc = differential.getElement().indexOf(diffMatches.get(0)) + 1; 1153 int ndl = findEndOfElement(differential, ndc); 1154 processPaths(indent + " ", result, base, differential, baseCursor + 1, ndc, nbl, ndl, url, webUrl, 1155 profileName + pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, 1156 resultPathBase, false, null, srcSD); 1157// throw new Error("Not done yet"); 1158// } else if (currentBase.getType().get(0).getCode().equals("BackboneElement") && diffMatches.size() > 0 && diffMatches.get(0).hasSliceName()) { 1159 } else if (currentBase.getType().get(0).getCode().equals("BackboneElement")) { 1160 // We need to copy children of the backbone element before we start messing 1161 // around with slices 1162 int nbl = findEndOfElement(base, baseCursor); 1163 for (int i = baseCursor + 1; i <= nbl; i++) { 1164 outcome = updateURLs(url, webUrl, base.getElement().get(i).copy()); 1165 result.getElement().add(outcome); 1166 } 1167 } 1168 1169 // now, we have two lists, base and diff. we're going to work through base, 1170 // looking for matches in diff. 1171 List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase); 1172 for (ElementDefinition baseItem : baseMatches) { 1173 baseCursor = base.getElement().indexOf(baseItem); 1174 outcome = updateURLs(url, webUrl, baseItem.copy()); 1175 updateFromBase(outcome, currentBase); 1176 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1177 outcome.setSlicing(null); 1178 if (!outcome.getPath().startsWith(resultPathBase)) 1179 throw new DefinitionException("Adding wrong path"); 1180 if (diffpos < diffMatches.size() 1181 && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) { 1182 // if there's a diff, we update the outcome with diff 1183 // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, 1184 // closed, url); 1185 // then process any children 1186 int nbl = findEndOfElement(base, baseCursor); 1187 int ndc = differential.getElement().indexOf(diffMatches.get(diffpos)); 1188 int ndl = findEndOfElement(differential, ndc); 1189 // now we process the base scope repeatedly for each instance of the item in the 1190 // differential list 1191 processPaths(indent + " ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, 1192 profileName + pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, 1193 resultPathBase, true, redirector, srcSD); 1194 // ok, done with that - now set the cursors for if this is the end 1195 baseCursor = nbl; 1196 diffCursor = ndl + 1; 1197 diffpos++; 1198 } else { 1199 result.getElement().add(outcome); 1200 baseCursor++; 1201 // just copy any children on the base 1202 while (baseCursor < base.getElement().size() 1203 && base.getElement().get(baseCursor).getPath().startsWith(path) 1204 && !base.getElement().get(baseCursor).getPath().equals(path)) { 1205 outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy()); 1206 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1207 if (!outcome.getPath().startsWith(resultPathBase)) 1208 throw new DefinitionException("Adding wrong path"); 1209 result.getElement().add(outcome); 1210 baseCursor++; 1211 } 1212 // Lloyd - add this for test T15 1213 baseCursor--; 1214 } 1215 } 1216 // finally, we process any remaining entries in diff, which are new (and which 1217 // are only allowed if the base wasn't closed 1218 if (closed && diffpos < diffMatches.size()) 1219 throw new DefinitionException( 1220 "The base snapshot marks a slicing as closed, but the differential tries to extend it in " + profileName 1221 + " at " + path + " (" + cpath + ")"); 1222 if (diffpos == diffMatches.size()) { 1223//Lloyd This was causing problems w/ Telus 1224// diffCursor++; 1225 } else { 1226 while (diffpos < diffMatches.size()) { 1227 ElementDefinition diffItem = diffMatches.get(diffpos); 1228 for (ElementDefinition baseItem : baseMatches) 1229 if (baseItem.getSliceName().equals(diffItem.getSliceName())) 1230 throw new DefinitionException("Named items are out of order in the slice"); 1231 outcome = updateURLs(url, webUrl, currentBase.copy()); 1232 // outcome = updateURLs(url, diffItem.copy()); 1233 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1234 updateFromBase(outcome, currentBase); 1235 outcome.setSlicing(null); 1236 if (!outcome.getPath().startsWith(resultPathBase)) 1237 throw new DefinitionException("Adding wrong path"); 1238 result.getElement().add(outcome); 1239 updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url, srcSD); 1240 removeStatusExtensions(outcome); 1241 // --- LM Added this 1242 diffCursor = differential.getElement().indexOf(diffItem) + 1; 1243 if (!outcome.getType().isEmpty() 1244 && (/* outcome.getType().get(0).getCode().equals("Extension") || */differential.getElement() 1245 .size() > diffCursor) 1246 && outcome.getPath().contains(".") && isDataType(outcome.getType())) { // don't want to do this for 1247 // the root, since that's base, 1248 // and we're already processing 1249 // it 1250 if (!baseWalksInto(base.getElement(), baseCursor)) { 1251 if (differential.getElement().size() > diffCursor && pathStartsWith( 1252 differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) { 1253 if (outcome.getType().size() > 1) 1254 for (TypeRefComponent t : outcome.getType()) { 1255 if (!t.getCode().equals("Reference")) 1256 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 1257 + differential.getElement().get(diffCursor).getPath() + ") and multiple types (" 1258 + typeCode(outcome.getType()) + ") in profile " + profileName); 1259 } 1260 TypeRefComponent t = outcome.getType().get(0); 1261 if (t.getCode().equals("BackboneElement")) { 1262 int baseStart = base.getElement().indexOf(currentBase) + 1; 1263 int baseMax = baseStart + 1; 1264 while (baseMax < base.getElement().size() 1265 && base.getElement().get(baseMax).getPath().startsWith(currentBase.getPath() + ".")) 1266 baseMax++; 1267 int start = diffCursor; 1268 while (differential.getElement().size() > diffCursor && pathStartsWith( 1269 differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) 1270 diffCursor++; 1271 processPaths(indent + " ", result, base, differential, baseStart, start - 1, baseMax - 1, 1272 diffCursor - 1, url, webUrl, profileName + pathTail(diffMatches, 0), 1273 base.getElement().get(0).getPath(), base.getElement().get(0).getPath(), trimDifferential, 1274 contextName, resultPathBase, false, redirector, srcSD); 1275 1276 } else { 1277 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 1278 // if (t.getCode().equals("Extension") && t.hasProfile() && 1279 // !t.getProfile().contains(":")) { 1280 // lloydfix dt = 1281 // } 1282 if (dt == null) 1283 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 1284 + differential.getElement().get(diffCursor).getPath() + ") for type " 1285 + typeCode(outcome.getType()) + " in profile " + profileName + ", but can't find type"); 1286 contextName = dt.getUrl(); 1287 int start = diffCursor; 1288 while (differential.getElement().size() > diffCursor && pathStartsWith( 1289 differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) 1290 diffCursor++; 1291 processPaths(indent + " ", result, dt.getSnapshot(), differential, 1292 1 /* starting again on the data type, but skip the root */, start - 1, 1293 dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, webUrl, 1294 profileName + pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), 1295 trimDifferential, contextName, resultPathBase, false, redirector, srcSD); 1296 } 1297 } else if (outcome.getType().get(0).getCode().equals("Extension")) { 1298 // Force URL to appear if we're dealing with an extension. (This is a kludge - 1299 // may need to drill down in other cases where we're slicing and the type has a 1300 // profile declaration that could be setting the fixed value) 1301 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 1302 for (ElementDefinition extEd : dt.getSnapshot().getElement()) { 1303 // We only want the children that aren't the root 1304 if (extEd.getPath().contains(".")) { 1305 ElementDefinition extUrlEd = updateURLs(url, webUrl, extEd.copy()); 1306 extUrlEd.setPath(fixedPathDest(outcome.getPath(), extUrlEd.getPath(), redirector, null)); 1307 // updateFromBase(extUrlEd, currentBase); 1308 markDerived(extUrlEd); 1309 result.getElement().add(extUrlEd); 1310 } 1311 } 1312 } 1313 } 1314 } 1315 // --- 1316 diffpos++; 1317 } 1318 } 1319 baseCursor++; 1320 } 1321 } 1322 } 1323 1324 int i = 0; 1325 for (ElementDefinition e : result.getElement()) { 1326 i++; 1327 if (e.hasMinElement() && e.getMinElement().getValue() == null) 1328 throw new Error("null min"); 1329 } 1330 return res; 1331 } 1332 1333 private void removeStatusExtensions(ElementDefinition outcome) { 1334 outcome.removeExtension(ToolingExtensions.EXT_FMM_LEVEL); 1335 outcome.removeExtension(ToolingExtensions.EXT_STANDARDS_STATUS); 1336 outcome.removeExtension(ToolingExtensions.EXT_NORMATIVE_VERSION); 1337 outcome.removeExtension(ToolingExtensions.EXT_WORKGROUP); 1338 } 1339 1340 private String descED(List<ElementDefinition> list, int index) { 1341 return index >= 0 && index < list.size() ? list.get(index).present() : "X"; 1342 } 1343 1344 private boolean baseHasChildren(StructureDefinitionSnapshotComponent base, ElementDefinition ed) { 1345 int index = base.getElement().indexOf(ed); 1346 if (index == -1 || index >= base.getElement().size() - 1) 1347 return false; 1348 String p = base.getElement().get(index + 1).getPath(); 1349 return isChildOf(p, ed.getPath()); 1350 } 1351 1352 private boolean isChildOf(String sub, String focus) { 1353 if (focus.endsWith("[x]")) { 1354 focus = focus.substring(0, focus.length() - 3); 1355 return sub.startsWith(focus); 1356 } else 1357 return sub.startsWith(focus + "."); 1358 } 1359 1360 private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, 1361 int baseLimit) { 1362 return baseLimit + 1; 1363 } 1364 1365 private String rootName(String cpath) { 1366 String t = tail(cpath); 1367 return t.replace("[x]", ""); 1368 } 1369 1370 private String determineTypeSlicePath(String path, String cpath) { 1371 String headP = path.substring(0, path.lastIndexOf(".")); 1372// String tailP = path.substring(path.lastIndexOf(".")+1); 1373 String tailC = cpath.substring(cpath.lastIndexOf(".") + 1); 1374 return headP + "." + tailC; 1375 } 1376 1377 private boolean isImplicitSlicing(ElementDefinition ed, String path) { 1378 if (ed == null || ed.getPath() == null || path == null) 1379 return false; 1380 if (path.equals(ed.getPath())) 1381 return false; 1382 boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length() - 3)); 1383 return ok; 1384 } 1385 1386 private boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) { 1387// if (diffMatches.size() < 2) 1388// return false; 1389 String p = diffMatches.get(0).getPath(); 1390 if (!p.endsWith("[x]") && !cPath.endsWith("[x]")) 1391 return false; 1392 typeList.clear(); 1393 String rn = tail(cPath); 1394 rn = rn.substring(0, rn.length() - 3); 1395 for (int i = 0; i < diffMatches.size(); i++) { 1396 ElementDefinition ed = diffMatches.get(i); 1397 String n = tail(ed.getPath()); 1398 if (!n.startsWith(rn)) 1399 return false; 1400 String s = n.substring(rn.length()); 1401 if (!s.contains(".")) { 1402 if (ed.hasSliceName() && ed.getType().size() == 1) { 1403 typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode())); 1404 } else if (!ed.hasSliceName() && !s.equals("[x]")) { 1405 if (isDataType(s)) 1406 typeList.add(new TypeSlice(ed, s)); 1407 else if (isConstrainedDataType(s)) 1408 typeList.add(new TypeSlice(ed, baseType(s))); 1409 else if (isPrimitive(Utilities.uncapitalize(s))) 1410 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 1411 } else if (!ed.hasSliceName() && s.equals("[x]")) 1412 typeList.add(new TypeSlice(ed, null)); 1413 } 1414 } 1415 return true; 1416 } 1417 1418 private List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, 1419 String path) { 1420 List<ElementRedirection> result = new ArrayList<ElementRedirection>(); 1421 result.addAll(redirector); 1422 result.add(new ElementRedirection(outcome, path)); 1423 return result; 1424 } 1425 1426 private List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) { 1427 List<TypeRefComponent> res = new ArrayList<TypeRefComponent>(); 1428 for (TypeRefComponent tr : type) { 1429 if (t.equals(tr.getWorkingCode())) 1430 res.add(tr); 1431 } 1432 return res; 1433 } 1434 1435 private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) { 1436 outcome.setContentReference(null); 1437 outcome.getType().clear(); // though it should be clear anyway 1438 outcome.getType().addAll(tgt.getType()); 1439 } 1440 1441 private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) { 1442 if (cursor >= elements.size()) 1443 return false; 1444 String path = elements.get(cursor).getPath(); 1445 String prevPath = elements.get(cursor - 1).getPath(); 1446 return path.startsWith(prevPath + "."); 1447 } 1448 1449 private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) 1450 throws FHIRFormatError { 1451 ElementDefinition res = profile.copy(); 1452 if (usage.hasSliceName()) 1453 res.setSliceName(usage.getSliceName()); 1454 if (usage.hasLabel()) 1455 res.setLabel(usage.getLabel()); 1456 for (Coding c : usage.getCode()) 1457 res.addCode(c); 1458 1459 if (usage.hasDefinition()) 1460 res.setDefinition(usage.getDefinition()); 1461 if (usage.hasShort()) 1462 res.setShort(usage.getShort()); 1463 if (usage.hasComment()) 1464 res.setComment(usage.getComment()); 1465 if (usage.hasRequirements()) 1466 res.setRequirements(usage.getRequirements()); 1467 for (StringType c : usage.getAlias()) 1468 res.addAlias(c.getValue()); 1469 if (usage.hasMin()) 1470 res.setMin(usage.getMin()); 1471 if (usage.hasMax()) 1472 res.setMax(usage.getMax()); 1473 1474 if (usage.hasFixed()) 1475 res.setFixed(usage.getFixed()); 1476 if (usage.hasPattern()) 1477 res.setPattern(usage.getPattern()); 1478 if (usage.hasExample()) 1479 res.setExample(usage.getExample()); 1480 if (usage.hasMinValue()) 1481 res.setMinValue(usage.getMinValue()); 1482 if (usage.hasMaxValue()) 1483 res.setMaxValue(usage.getMaxValue()); 1484 if (usage.hasMaxLength()) 1485 res.setMaxLength(usage.getMaxLength()); 1486 if (usage.hasMustSupport()) 1487 res.setMustSupport(usage.getMustSupport()); 1488 if (usage.hasBinding()) 1489 res.setBinding(usage.getBinding().copy()); 1490 for (ElementDefinitionConstraintComponent c : usage.getConstraint()) 1491 res.addConstraint(c); 1492 for (Extension e : usage.getExtension()) { 1493 if (!res.hasExtension(e.getUrl())) 1494 res.addExtension(e.copy()); 1495 } 1496 1497 return res; 1498 } 1499 1500 private boolean checkExtensionDoco(ElementDefinition base) { 1501 // see task 3970. For an extension, there's no point copying across all the 1502 // underlying definitional stuff 1503 boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") 1504 || base.getPath().endsWith(".modifierExtension"); 1505 if (isExtension) { 1506 base.setDefinition("An Extension"); 1507 base.setShort("Extension"); 1508 base.setCommentElement(null); 1509 base.setRequirementsElement(null); 1510 base.getAlias().clear(); 1511 base.getMapping().clear(); 1512 } 1513 return isExtension; 1514 } 1515 1516 private String pathTail(List<ElementDefinition> diffMatches, int i) { 1517 1518 ElementDefinition d = diffMatches.get(i); 1519 String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".") + 1) : d.getPath(); 1520 return "." + s 1521 + (d.hasType() && d.getType().get(0).hasProfile() ? "[" + d.getType().get(0).getProfile() + "]" : ""); 1522 } 1523 1524 private void markDerived(ElementDefinition outcome) { 1525 for (ElementDefinitionConstraintComponent inv : outcome.getConstraint()) 1526 inv.setUserData(IS_DERIVED, true); 1527 } 1528 1529 private String summarizeSlicing(ElementDefinitionSlicingComponent slice) { 1530 StringBuilder b = new StringBuilder(); 1531 boolean first = true; 1532 for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) { 1533 if (first) 1534 first = false; 1535 else 1536 b.append(", "); 1537 b.append(d); 1538 } 1539 b.append("("); 1540 if (slice.hasOrdered()) 1541 b.append(slice.getOrderedElement().asStringValue()); 1542 b.append("/"); 1543 if (slice.hasRules()) 1544 b.append(slice.getRules().toCode()); 1545 b.append(")"); 1546 if (slice.hasDescription()) { 1547 b.append(" \""); 1548 b.append(slice.getDescription()); 1549 b.append("\""); 1550 } 1551 return b.toString(); 1552 } 1553 1554 private void updateFromBase(ElementDefinition derived, ElementDefinition base) { 1555 if (base.hasBase()) { 1556 if (!derived.hasBase()) 1557 derived.setBase(new ElementDefinitionBaseComponent()); 1558 derived.getBase().setPath(base.getBase().getPath()); 1559 derived.getBase().setMin(base.getBase().getMin()); 1560 derived.getBase().setMax(base.getBase().getMax()); 1561 } else { 1562 if (!derived.hasBase()) 1563 derived.setBase(new ElementDefinitionBaseComponent()); 1564 derived.getBase().setPath(base.getPath()); 1565 derived.getBase().setMin(base.getMin()); 1566 derived.getBase().setMax(base.getMax()); 1567 } 1568 } 1569 1570 private boolean pathStartsWith(String p1, String p2) { 1571 return p1.startsWith(p2); 1572 } 1573 1574 private boolean pathMatches(String p1, String p2) { 1575 return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length() - 3)) 1576 && !p1.substring(p2.length() - 3).contains(".")); 1577 } 1578 1579 private String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) { 1580 if (contextPath == null) 1581 return pathSimple; 1582// String ptail = pathSimple.substring(contextPath.length() + 1); 1583 if (redirector.size() > 0) { 1584 String ptail = pathSimple.substring(contextPath.length() + 1); 1585 return redirector.get(redirector.size() - 1).getPath() + "." + ptail; 1586// return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1); 1587 } else { 1588 String ptail = pathSimple.substring(pathSimple.indexOf(".") + 1); 1589 return contextPath + "." + ptail; 1590 } 1591 } 1592 1593 private String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, 1594 String redirectSource) { 1595 String s; 1596 if (contextPath == null) 1597 s = pathSimple; 1598 else { 1599 if (redirector.size() > 0) { 1600 String ptail = pathSimple.substring(redirectSource.length() + 1); 1601 // ptail = ptail.substring(ptail.indexOf(".")+1); 1602 s = contextPath + "." + /* tail(redirector.getPath())+"."+ */ptail; 1603 } else { 1604 String ptail = pathSimple.substring(pathSimple.indexOf(".") + 1); 1605 s = contextPath + "." + ptail; 1606 } 1607 } 1608 return s; 1609 } 1610 1611 private StructureDefinition getProfileForDataType(TypeRefComponent type) { 1612 StructureDefinition sd = null; 1613 if (type.hasProfile()) { 1614 sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue()); 1615 if (sd == null) 1616 System.out.println("Failed to find referenced profile: " + type.getProfile()); 1617 } 1618 if (sd == null) 1619 sd = context.fetchTypeDefinition(type.getWorkingCode()); 1620 if (sd == null) 1621 System.out.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM 1622 return sd; 1623 } 1624 1625 private StructureDefinition getProfileForDataType(String type) { 1626 StructureDefinition sd = context.fetchTypeDefinition(type); 1627 if (sd == null) 1628 System.out.println("XX: failed to find profle for type: " + type); // debug GJM 1629 return sd; 1630 } 1631 1632 public static String typeCode(List<TypeRefComponent> types) { 1633 StringBuilder b = new StringBuilder(); 1634 boolean first = true; 1635 for (TypeRefComponent type : types) { 1636 if (first) 1637 first = false; 1638 else 1639 b.append(", "); 1640 b.append(type.getWorkingCode()); 1641 if (type.hasTargetProfile()) 1642 b.append("{" + type.getTargetProfile() + "}"); 1643 else if (type.hasProfile()) 1644 b.append("{" + type.getProfile() + "}"); 1645 } 1646 return b.toString(); 1647 } 1648 1649 private boolean isDataType(List<TypeRefComponent> types) { 1650 if (types.isEmpty()) 1651 return false; 1652 for (TypeRefComponent type : types) { 1653 String t = type.getWorkingCode(); 1654 if (!isDataType(t) && !isPrimitive(t)) 1655 return false; 1656 } 1657 return true; 1658 } 1659 1660 /** 1661 * Finds internal references in an Element's Binding and StructureDefinition 1662 * references (in TypeRef) and bases them on the given url 1663 * 1664 * @param url - the base url to use to turn internal references into 1665 * absolute references 1666 * @param element - the Element to update 1667 * @return - the updated Element 1668 */ 1669 private ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) { 1670 if (element != null) { 1671 ElementDefinition defn = element; 1672 if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#")) 1673 defn.getBinding().setValueSet(url + defn.getBinding().getValueSet()); 1674 for (TypeRefComponent t : defn.getType()) { 1675 for (UriType u : t.getProfile()) { 1676 if (u.getValue().startsWith("#")) 1677 u.setValue(url + t.getProfile()); 1678 } 1679 for (UriType u : t.getTargetProfile()) { 1680 if (u.getValue().startsWith("#")) 1681 u.setValue(url + t.getTargetProfile()); 1682 } 1683 } 1684 if (webUrl != null) { 1685 // also, must touch up the markdown 1686 if (element.hasDefinition()) 1687 element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl)); 1688 if (element.hasComment()) 1689 element.setComment(processRelativeUrls(element.getComment(), webUrl)); 1690 if (element.hasRequirements()) 1691 element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl)); 1692 if (element.hasMeaningWhenMissing()) 1693 element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl)); 1694 } 1695 } 1696 return element; 1697 } 1698 1699 private String processRelativeUrls(String markdown, String webUrl) { 1700 StringBuilder b = new StringBuilder(); 1701 int i = 0; 1702 while (i < markdown.length()) { 1703 if (i < markdown.length() - 3 && markdown.substring(i, i + 2).equals("](")) { 1704 int j = i + 2; 1705 while (j < markdown.length() && markdown.charAt(j) != ')') 1706 j++; 1707 if (j < markdown.length()) { 1708 String url = markdown.substring(i + 2, j); 1709 if (!Utilities.isAbsoluteUrl(url)) { 1710 b.append("]("); 1711 b.append(webUrl); 1712 i = i + 1; 1713 } else 1714 b.append(markdown.charAt(i)); 1715 } else 1716 b.append(markdown.charAt(i)); 1717 } else { 1718 b.append(markdown.charAt(i)); 1719 } 1720 i++; 1721 } 1722 return b.toString(); 1723 } 1724 1725 private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) { 1726 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1727 String path = current.getPath(); 1728 int cursor = list.indexOf(current) + 1; 1729 while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) { 1730 if (pathMatches(list.get(cursor).getPath(), path)) 1731 result.add(list.get(cursor)); 1732 cursor++; 1733 } 1734 return result; 1735 } 1736 1737 private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) { 1738 if (src.hasOrderedElement()) 1739 dst.setOrderedElement(src.getOrderedElement().copy()); 1740 if (src.hasDiscriminator()) { 1741 // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll 1742 // because it uses object equality, not string equality 1743 for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) { 1744 boolean found = false; 1745 for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) { 1746 if (matches(d, s)) { 1747 found = true; 1748 break; 1749 } 1750 } 1751 if (!found) 1752 dst.getDiscriminator().add(s); 1753 } 1754 } 1755 if (src.hasRulesElement()) 1756 dst.setRulesElement(src.getRulesElement().copy()); 1757 } 1758 1759 private boolean orderMatches(BooleanType diff, BooleanType base) { 1760 return (diff == null) || (base == null) || (diff.getValue() == base.getValue()); 1761 } 1762 1763 private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, 1764 List<ElementDefinitionSlicingDiscriminatorComponent> base) { 1765 if (diff.isEmpty() || base.isEmpty()) 1766 return true; 1767 if (diff.size() != base.size()) 1768 return false; 1769 for (int i = 0; i < diff.size(); i++) 1770 if (!matches(diff.get(i), base.get(i))) 1771 return false; 1772 return true; 1773 } 1774 1775 private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, 1776 ElementDefinitionSlicingDiscriminatorComponent c2) { 1777 return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath()); 1778 } 1779 1780 private boolean ruleMatches(SlicingRules diff, SlicingRules base) { 1781 return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) 1782 || ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED)); 1783 } 1784 1785 private boolean isSlicedToOneOnly(ElementDefinition e) { 1786 return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); 1787 } 1788 1789 private ElementDefinitionSlicingComponent makeExtensionSlicing() { 1790 ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); 1791 slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); 1792 slice.setOrdered(false); 1793 slice.setRules(SlicingRules.OPEN); 1794 return slice; 1795 } 1796 1797 private boolean isExtension(ElementDefinition currentBase) { 1798 return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension"); 1799 } 1800 1801 private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, 1802 List<ElementDefinition> base, boolean allowSlices) throws DefinitionException { 1803 end = Math.min(context.getElement().size(), end); 1804 start = Math.max(0, start); 1805 1806 for (int i = start; i <= end; i++) { 1807 String statedPath = context.getElement().get(i).getPath(); 1808 if (statedPath.startsWith(path + ".")) { 1809 return true; 1810 } else if (!statedPath.endsWith(path)) 1811 break; 1812 } 1813 return false; 1814 } 1815 1816 private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, 1817 int start, int end, String profileName) throws DefinitionException { 1818 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1819 for (int i = start; i <= end; i++) { 1820 String statedPath = context.getElement().get(i).getPath(); 1821 if (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 1822 && statedPath.substring(0, path.length() - 3).equals(path.substring(0, path.length() - 3)) 1823 && (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains(".")))) { 1824 /* 1825 * Commenting this out because it raises warnings when profiling inherited 1826 * elements. For example, Error: unknown element 'Bundle.meta.profile' (or it is 1827 * out of order) in profile ... (looking for 'Bundle.entry') Not sure we have 1828 * enough information here to do the check properly. Might be better done when 1829 * we're sorting the profile? 1830 * 1831 * if (i != start && result.isEmpty() && 1832 * !path.startsWith(context.getElement().get(start).getPath())) messages.add(new 1833 * ValidationMessage(Source.ProfileValidator, IssueType.VALUE, 1834 * "StructureDefinition.differential.element["+Integer.toString(start)+"]", 1835 * "Error: unknown element '"+context.getElement().get(start).getPath() 1836 * +"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')", 1837 * IssueSeverity.WARNING)); 1838 * 1839 */ 1840 result.add(context.getElement().get(i)); 1841 } 1842 } 1843 return result; 1844 } 1845 1846 private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) { 1847 int result = cursor; 1848 String path = context.getElement().get(cursor).getPath() + "."; 1849 while (result < context.getElement().size() - 1 && context.getElement().get(result + 1).getPath().startsWith(path)) 1850 result++; 1851 return result; 1852 } 1853 1854 private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) { 1855 int result = cursor; 1856 String path = context.getElement().get(cursor).getPath() + "."; 1857 while (result < context.getElement().size() - 1 && context.getElement().get(result + 1).getPath().startsWith(path)) 1858 result++; 1859 return result; 1860 } 1861 1862 private boolean unbounded(ElementDefinition definition) { 1863 StringType max = definition.getMaxElement(); 1864 if (max == null) 1865 return false; // this is not valid 1866 if (max.getValue().equals("1")) 1867 return false; 1868 if (max.getValue().equals("0")) 1869 return false; 1870 return true; 1871 } 1872 1873 private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, 1874 boolean trimDifferential, String purl, StructureDefinition srcSD) throws DefinitionException, FHIRException { 1875 source.setUserData(GENERATED_IN_SNAPSHOT, dest); 1876 // we start with a clone of the base profile ('dest') and we copy from the 1877 // profile ('source') 1878 // over the top for anything the source has 1879 ElementDefinition base = dest; 1880 ElementDefinition derived = source; 1881 derived.setUserData(DERIVATION_POINTER, base); 1882 boolean isExtension = checkExtensionDoco(base); 1883 1884 // Before applying changes, apply them to what's in the profile 1885 // TODO: follow Chris's rules - Done by Lloyd 1886 StructureDefinition profile = null; 1887 if (base.hasSliceName()) 1888 profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() 1889 ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue()) 1890 : null; 1891 if (profile == null) 1892 profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() 1893 ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue()) 1894 : null; 1895 if (profile != null) { 1896 ElementDefinition e = profile.getSnapshot().getElement().get(0); 1897 base.setDefinition(e.getDefinition()); 1898 base.setShort(e.getShort()); 1899 if (e.hasCommentElement()) 1900 base.setCommentElement(e.getCommentElement()); 1901 if (e.hasRequirementsElement()) 1902 base.setRequirementsElement(e.getRequirementsElement()); 1903 base.getAlias().clear(); 1904 base.getAlias().addAll(e.getAlias()); 1905 base.getMapping().clear(); 1906 base.getMapping().addAll(e.getMapping()); 1907 } 1908 if (derived != null) { 1909 if (derived.hasSliceName()) { 1910 base.setSliceName(derived.getSliceName()); 1911 } 1912 1913 if (derived.hasShortElement()) { 1914 if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false)) 1915 base.setShortElement(derived.getShortElement().copy()); 1916 else if (trimDifferential) 1917 derived.setShortElement(null); 1918 else if (derived.hasShortElement()) 1919 derived.getShortElement().setUserData(DERIVATION_EQUALS, true); 1920 } 1921 1922 if (derived.hasDefinitionElement()) { 1923 if (derived.getDefinition().startsWith("...")) 1924 base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition())); 1925 else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) 1926 base.setDefinitionElement(derived.getDefinitionElement().copy()); 1927 else if (trimDifferential) 1928 derived.setDefinitionElement(null); 1929 else if (derived.hasDefinitionElement()) 1930 derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true); 1931 } 1932 1933 if (derived.hasCommentElement()) { 1934 if (derived.getComment().startsWith("...")) 1935 base.setComment(Utilities.appendDerivedTextToBase(base.getComment(), derived.getComment())); 1936 else if (derived.hasCommentElement() != base.hasCommentElement() 1937 || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false)) 1938 base.setCommentElement(derived.getCommentElement().copy()); 1939 else if (trimDifferential) 1940 base.setCommentElement(derived.getCommentElement().copy()); 1941 else if (derived.hasCommentElement()) 1942 derived.getCommentElement().setUserData(DERIVATION_EQUALS, true); 1943 } 1944 1945 if (derived.hasLabelElement()) { 1946 if (derived.getLabel().startsWith("...")) 1947 base.setLabel(Utilities.appendDerivedTextToBase(base.getLabel(), derived.getLabel())); 1948 else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false)) 1949 base.setLabelElement(derived.getLabelElement().copy()); 1950 else if (trimDifferential) 1951 base.setLabelElement(derived.getLabelElement().copy()); 1952 else if (derived.hasLabelElement()) 1953 derived.getLabelElement().setUserData(DERIVATION_EQUALS, true); 1954 } 1955 1956 if (derived.hasRequirementsElement()) { 1957 if (derived.getRequirements().startsWith("...")) 1958 base.setRequirements(Utilities.appendDerivedTextToBase(base.getRequirements(), derived.getRequirements())); 1959 else if (!base.hasRequirementsElement() 1960 || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false)) 1961 base.setRequirementsElement(derived.getRequirementsElement().copy()); 1962 else if (trimDifferential) 1963 base.setRequirementsElement(derived.getRequirementsElement().copy()); 1964 else if (derived.hasRequirementsElement()) 1965 derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true); 1966 } 1967 // sdf-9 1968 if (derived.hasRequirements() && !base.getPath().contains(".")) 1969 derived.setRequirements(null); 1970 if (base.hasRequirements() && !base.getPath().contains(".")) 1971 base.setRequirements(null); 1972 1973 if (derived.hasAlias()) { 1974 if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false)) 1975 for (StringType s : derived.getAlias()) { 1976 if (!base.hasAlias(s.getValue())) 1977 base.getAlias().add(s.copy()); 1978 } 1979 else if (trimDifferential) 1980 derived.getAlias().clear(); 1981 else 1982 for (StringType t : derived.getAlias()) 1983 t.setUserData(DERIVATION_EQUALS, true); 1984 } 1985 1986 if (derived.hasMinElement()) { 1987 if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { 1988 if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do 1989 // not apply 1990 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 1991 pn + "." + source.getPath(), 1992 "Element " + base.getPath() + ": derived min (" + Integer.toString(derived.getMin()) 1993 + ") cannot be less than base min (" + Integer.toString(base.getMin()) + ")", 1994 ValidationMessage.IssueSeverity.ERROR)); 1995 base.setMinElement(derived.getMinElement().copy()); 1996 } else if (trimDifferential) 1997 derived.setMinElement(null); 1998 else 1999 derived.getMinElement().setUserData(DERIVATION_EQUALS, true); 2000 } 2001 2002 if (derived.hasMaxElement()) { 2003 if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { 2004 if (isLargerMax(derived.getMax(), base.getMax())) 2005 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2006 pn + "." + source.getPath(), 2007 "Element " + base.getPath() + ": derived max (" + derived.getMax() 2008 + ") cannot be greater than base max (" + base.getMax() + ")", 2009 ValidationMessage.IssueSeverity.ERROR)); 2010 base.setMaxElement(derived.getMaxElement().copy()); 2011 } else if (trimDifferential) 2012 derived.setMaxElement(null); 2013 else 2014 derived.getMaxElement().setUserData(DERIVATION_EQUALS, true); 2015 } 2016 2017 if (derived.hasFixed()) { 2018 if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) { 2019 base.setFixed(derived.getFixed().copy()); 2020 } else if (trimDifferential) 2021 derived.setFixed(null); 2022 else 2023 derived.getFixed().setUserData(DERIVATION_EQUALS, true); 2024 } 2025 2026 if (derived.hasPattern()) { 2027 if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) { 2028 base.setPattern(derived.getPattern().copy()); 2029 } else if (trimDifferential) 2030 derived.setPattern(null); 2031 else 2032 derived.getPattern().setUserData(DERIVATION_EQUALS, true); 2033 } 2034 2035 for (ElementDefinitionExampleComponent ex : derived.getExample()) { 2036 boolean found = false; 2037 for (ElementDefinitionExampleComponent exS : base.getExample()) 2038 if (Base.compareDeep(ex, exS, false)) 2039 found = true; 2040 if (!found) 2041 base.addExample(ex.copy()); 2042 else if (trimDifferential) 2043 derived.getExample().remove(ex); 2044 else 2045 ex.setUserData(DERIVATION_EQUALS, true); 2046 } 2047 2048 if (derived.hasMaxLengthElement()) { 2049 if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false)) 2050 base.setMaxLengthElement(derived.getMaxLengthElement().copy()); 2051 else if (trimDifferential) 2052 derived.setMaxLengthElement(null); 2053 else 2054 derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true); 2055 } 2056 2057 if (derived.hasMaxValue()) { 2058 if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false)) 2059 base.setMaxValue(derived.getMaxValue().copy()); 2060 else if (trimDifferential) 2061 derived.setMaxValue(null); 2062 else 2063 derived.getMaxValue().setUserData(DERIVATION_EQUALS, true); 2064 } 2065 2066 if (derived.hasMinValue()) { 2067 if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false)) 2068 base.setMinValue(derived.getMinValue().copy()); 2069 else if (trimDifferential) 2070 derived.setMinValue(null); 2071 else 2072 derived.getMinValue().setUserData(DERIVATION_EQUALS, true); 2073 } 2074 2075 // todo: what to do about conditions? 2076 // condition : id 0..* 2077 2078 if (derived.hasMustSupportElement()) { 2079 if (!(base.hasMustSupportElement() 2080 && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))) 2081 base.setMustSupportElement(derived.getMustSupportElement().copy()); 2082 else if (trimDifferential) 2083 derived.setMustSupportElement(null); 2084 else 2085 derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true); 2086 } 2087 2088 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 2089 // but extensions can change isModifier 2090 if (isExtension) { 2091 if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() 2092 && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) 2093 base.setIsModifierElement(derived.getIsModifierElement().copy()); 2094 else if (trimDifferential) 2095 derived.setIsModifierElement(null); 2096 else if (derived.hasIsModifierElement()) 2097 derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true); 2098 if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() 2099 && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) 2100 base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy()); 2101 else if (trimDifferential) 2102 derived.setIsModifierReasonElement(null); 2103 else if (derived.hasIsModifierReasonElement()) 2104 derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true); 2105 } 2106 2107 if (derived.hasBinding()) { 2108 if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 2109 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED 2110 && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 2111 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2112 pn + "." + derived.getPath(), 2113 "illegal attempt to change the binding on " + derived.getPath() + " from " 2114 + base.getBinding().getStrength().toCode() + " to " + derived.getBinding().getStrength().toCode(), 2115 ValidationMessage.IssueSeverity.ERROR)); 2116// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 2117 else if (base.hasBinding() && derived.hasBinding() 2118 && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() 2119 && derived.getBinding().hasValueSet()) { 2120 ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet()); 2121 ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet()); 2122 if (baseVs == null) { 2123 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2124 pn + "." + base.getPath(), "Binding " + base.getBinding().getValueSet() + " could not be located", 2125 ValidationMessage.IssueSeverity.WARNING)); 2126 } else if (contextVs == null) { 2127 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2128 pn + "." + derived.getPath(), 2129 "Binding " + derived.getBinding().getValueSet() + " could not be located", 2130 ValidationMessage.IssueSeverity.WARNING)); 2131 } else { 2132 ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false); 2133 ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false); 2134 if (expBase.getValueset() == null) 2135 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2136 pn + "." + base.getPath(), "Binding " + base.getBinding().getValueSet() + " could not be expanded", 2137 ValidationMessage.IssueSeverity.WARNING)); 2138 else if (expDerived.getValueset() == null) 2139 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2140 pn + "." + derived.getPath(), 2141 "Binding " + derived.getBinding().getValueSet() + " could not be expanded", 2142 ValidationMessage.IssueSeverity.WARNING)); 2143 else if (!isSubset(expBase.getValueset(), expDerived.getValueset())) 2144 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 2145 pn + "." + derived.getPath(), "Binding " + derived.getBinding().getValueSet() 2146 + " is not a subset of binding " + base.getBinding().getValueSet(), 2147 ValidationMessage.IssueSeverity.ERROR)); 2148 2149 } 2150 } 2151 base.setBinding(derived.getBinding().copy()); 2152 } else if (trimDifferential) 2153 derived.setBinding(null); 2154 else 2155 derived.getBinding().setUserData(DERIVATION_EQUALS, true); 2156 } // else if (base.hasBinding() && doesn't have bindable type ) 2157 // base 2158 2159 if (derived.hasIsSummaryElement()) { 2160 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) { 2161 if (base.hasIsSummary()) 2162 throw new Error("Error in profile " + pn + " at " + derived.getPath() + ": Base isSummary = " 2163 + base.getIsSummaryElement().asStringValue() + ", derived isSummary = " 2164 + derived.getIsSummaryElement().asStringValue()); 2165 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 2166 } else if (trimDifferential) 2167 derived.setIsSummaryElement(null); 2168 else 2169 derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true); 2170 } 2171 2172 if (derived.hasType()) { 2173 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 2174 if (base.hasType()) { 2175 for (TypeRefComponent ts : derived.getType()) { 2176// if (!ts.hasCode()) { // ommitted in the differential; copy it over.... 2177// if (base.getType().size() > 1) 2178// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": constrained type code must be present if there are multiple types ("+base.typeSummary()+")"); 2179// if (base.getType().get(0).getCode() != null) 2180// ts.setCode(base.getType().get(0).getCode()); 2181// } 2182 boolean ok = false; 2183 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2184 String t = ts.getWorkingCode(); 2185 for (TypeRefComponent td : base.getType()) { 2186 ; 2187 String tt = td.getWorkingCode(); 2188 b.append(tt); 2189 if (td.hasCode() 2190 && (tt.equals(t) || "Extension".equals(tt) || (t.equals("uri") && tt.equals("string")) || // work 2191 // around 2192 // for old 2193 // badly 2194 // generated 2195 // SDs 2196 "Element".equals(tt) || "*".equals(tt) 2197 || (("Resource".equals(tt) || ("DomainResource".equals(tt)) && pkp.isResource(t))))) 2198 ok = true; 2199 } 2200 if (!ok) 2201 throw new DefinitionException("StructureDefinition " + pn + " at " + derived.getPath() 2202 + ": illegal constrained type " + t + " from " + b.toString() + " in " + srcSD.getUrl()); 2203 } 2204 } 2205 base.getType().clear(); 2206 for (TypeRefComponent t : derived.getType()) { 2207 TypeRefComponent tt = t.copy(); 2208// tt.setUserData(DERIVATION_EQUALS, true); 2209 base.getType().add(tt); 2210 } 2211 } else if (trimDifferential) 2212 derived.getType().clear(); 2213 else 2214 for (TypeRefComponent t : derived.getType()) 2215 t.setUserData(DERIVATION_EQUALS, true); 2216 } 2217 2218 if (derived.hasMapping()) { 2219 // todo: mappings are not cumulative - one replaces another 2220 if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) { 2221 for (ElementDefinitionMappingComponent s : derived.getMapping()) { 2222 boolean found = false; 2223 for (ElementDefinitionMappingComponent d : base.getMapping()) { 2224 found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap())); 2225 } 2226 if (!found) 2227 base.getMapping().add(s); 2228 } 2229 } else if (trimDifferential) 2230 derived.getMapping().clear(); 2231 else 2232 for (ElementDefinitionMappingComponent t : derived.getMapping()) 2233 t.setUserData(DERIVATION_EQUALS, true); 2234 } 2235 2236 // todo: constraints are cumulative. there is no replacing 2237 for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 2238 s.setUserData(IS_DERIVED, true); 2239 if (!s.hasSource()) 2240 s.setSource(base.getId()); 2241 } 2242 if (derived.hasConstraint()) { 2243 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 2244 ElementDefinitionConstraintComponent inv = s.copy(); 2245 base.getConstraint().add(inv); 2246 } 2247 } 2248 2249 // now, check that we still have a bindable type; if not, delete the binding - 2250 // see task 8477 2251 if (dest.hasBinding() && !hasBindableType(dest)) 2252 dest.setBinding(null); 2253 2254 // finally, we copy any extensions from source to dest 2255 for (Extension ex : derived.getExtension()) { 2256 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ex.getUrl()); 2257 if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) 2258 ToolingExtensions.removeExtension(dest, ex.getUrl()); 2259 dest.addExtension(ex.copy()); 2260 } 2261 } 2262 } 2263 2264 private boolean hasBindableType(ElementDefinition ed) { 2265 for (TypeRefComponent tr : ed.getType()) { 2266 if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code")) 2267 return true; 2268 } 2269 return false; 2270 } 2271 2272 private boolean isLargerMax(String derived, String base) { 2273 if ("*".equals(base)) 2274 return false; 2275 if ("*".equals(derived)) 2276 return true; 2277 return Integer.parseInt(derived) > Integer.parseInt(base); 2278 } 2279 2280 private boolean isSubset(ValueSet expBase, ValueSet expDerived) { 2281 return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion()); 2282 } 2283 2284 private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, 2285 ValueSetExpansionComponent expansion) { 2286 for (ValueSetExpansionContainsComponent cc : contains) { 2287 if (!inExpansion(cc, expansion.getContains())) 2288 return false; 2289 if (!codesInExpansion(cc.getContains(), expansion)) 2290 return false; 2291 } 2292 return true; 2293 } 2294 2295 private boolean inExpansion(ValueSetExpansionContainsComponent cc, 2296 List<ValueSetExpansionContainsComponent> contains) { 2297 for (ValueSetExpansionContainsComponent cc1 : contains) { 2298 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) 2299 return true; 2300 if (inExpansion(cc, cc1.getContains())) 2301 return true; 2302 } 2303 return false; 2304 } 2305 2306 public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { 2307 for (ElementDefinition edb : base.getSnapshot().getElement()) { 2308 if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { 2309 ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); 2310 if (edm == null) { 2311 ElementDefinition edd = derived.getDifferential().addElement(); 2312 edd.setPath(edb.getPath()); 2313 edd.setMax("0"); 2314 } else if (edb.hasSlicing()) { 2315 closeChildren(base, edb, derived, edm); 2316 } 2317 } 2318 } 2319 sortDifferential(base, derived, derived.getName(), new ArrayList<String>()); 2320 } 2321 2322 private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, 2323 ElementDefinition edm) { 2324 String path = edb.getPath() + "."; 2325 int baseStart = base.getSnapshot().getElement().indexOf(edb); 2326 int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart + 1); 2327 int diffStart = derived.getDifferential().getElement().indexOf(edm); 2328 int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart + 1); 2329 2330 for (int cBase = baseStart; cBase < baseEnd; cBase++) { 2331 ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); 2332 if (isImmediateChild(edBase, edb)) { 2333 ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, 2334 diffEnd); 2335 if (edMatch == null) { 2336 ElementDefinition edd = derived.getDifferential().addElement(); 2337 edd.setPath(edBase.getPath()); 2338 edd.setMax("0"); 2339 } else { 2340 closeChildren(base, edBase, derived, edMatch); 2341 } 2342 } 2343 } 2344 } 2345 2346 private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) { 2347 String path = ed.getPath() + "."; 2348 while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) 2349 cursor++; 2350 return cursor; 2351 } 2352 2353 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) { 2354 for (ElementDefinition t : list) 2355 if (t.getPath().equals(ed.getPath())) 2356 return t; 2357 return null; 2358 } 2359 2360 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) { 2361 for (int i = start; i < end; i++) { 2362 ElementDefinition t = list.get(i); 2363 if (t.getPath().equals(ed.getPath())) 2364 return t; 2365 } 2366 return null; 2367 } 2368 2369 private boolean isImmediateChild(ElementDefinition ed) { 2370 String p = ed.getPath(); 2371 if (!p.contains(".")) 2372 return false; 2373 p = p.substring(p.indexOf(".") + 1); 2374 return !p.contains("."); 2375 } 2376 2377 private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { 2378 String p = candidate.getPath(); 2379 if (!p.contains(".")) 2380 return false; 2381 if (!p.startsWith(base.getPath() + ".")) 2382 return false; 2383 p = p.substring(base.getPath().length() + 1); 2384 return !p.contains("."); 2385 } 2386 2387 public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, 2388 boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) 2389 throws IOException, FHIRException { 2390 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics, true); 2391 TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false, TableGenerationMode.XML); 2392 2393 boolean deep = false; 2394 String m = ""; 2395 boolean vdeep = false; 2396 if (ed.getSnapshot().getElementFirstRep().getIsModifier()) 2397 m = "modifier_"; 2398 for (ElementDefinition eld : ed.getSnapshot().getElement()) { 2399 deep = deep || eld.getPath().contains("Extension.extension."); 2400 vdeep = vdeep || eld.getPath().contains("Extension.extension.extension."); 2401 } 2402 Row r = gen.new Row(); 2403 model.getRows().add(r); 2404 String en; 2405 if (!full) 2406 en = ed.getName(); 2407 else if (ed.getSnapshot().getElement().get(0).getIsModifier()) 2408 en = "modifierExtension"; 2409 else 2410 en = "extension"; 2411 2412 r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile + "-definitions.html#extension." + ed.getName(), 2413 en, null, null)); 2414 r.getCells().add(gen.new Cell()); 2415 r.getCells().add(gen.new Cell(null, null, 2416 describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null)); 2417 2418 ElementDefinition ved = null; 2419 if (full || vdeep) { 2420 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 2421 2422 r.setIcon(deep ? "icon_" + m + "extension_complex.png" : "icon_extension_simple.png", 2423 deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX 2424 : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2425 List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), 2426 ed.getSnapshot().getElement().get(0)); 2427 for (ElementDefinition child : children) 2428 if (!child.getPath().endsWith(".id")) 2429 genElement(defFile == null ? "" : defFile + "-definitions.html#extension.", gen, r.getSubRows(), child, 2430 ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath, imagePath, true, false, false, 2431 false, null); 2432 } else if (deep) { 2433 List<ElementDefinition> children = new ArrayList<ElementDefinition>(); 2434 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 2435 if (ted.getPath().equals("Extension.extension")) 2436 children.add(ted); 2437 } 2438 2439 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 2440 r.setIcon("icon_" + m + "extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2441 2442 for (ElementDefinition c : children) { 2443 ved = getValueFor(ed, c); 2444 ElementDefinition ued = getUrlFor(ed, c); 2445 if (ved != null && ued != null) { 2446 Row r1 = gen.new Row(); 2447 r.getSubRows().add(r1); 2448 r1.getCells() 2449 .add(gen.new Cell(null, defFile == null ? "" : defFile + "-definitions.html#extension." + ed.getName(), 2450 ((UriType) ued.getFixed()).getValue(), null, null)); 2451 r1.getCells().add(gen.new Cell()); 2452 r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null)); 2453 genTypes(gen, r1, ved, defFile, ed, corePath, imagePath); 2454 Cell cell = gen.new Cell(); 2455 cell.addMarkdown(c.getDefinition()); 2456 r1.getCells().add(cell); 2457 r1.setIcon("icon_" + m + "extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2458 } 2459 } 2460 } else { 2461 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 2462 if (ted.getPath().startsWith("Extension.value")) 2463 ved = ted; 2464 } 2465 2466 genTypes(gen, r, ved, defFile, ed, corePath, imagePath); 2467 2468 r.setIcon("icon_" + m + "extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2469 } 2470 Cell c = gen.new Cell("", "", "URL = " + ed.getUrl(), null, null); 2471 Piece cc = gen.new Piece(null, ed.getName() + ": ", null); 2472 c.addPiece(gen.new Piece("br")).addPiece(cc); 2473 c.addMarkdown(ed.getDescription()); 2474 2475 if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) { 2476 c.addPiece(gen.new Piece("br")); 2477 BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath()); 2478 c.getPieces().add(checkForNoChange(ved.getBinding(), 2479 gen.new Piece(null, translate("sd.table", "Binding") + ": ", null).addStyle("font-weight:bold"))); 2480 c.getPieces() 2481 .add(checkForNoChange(ved.getBinding(), 2482 gen.new Piece( 2483 br.url == null ? null 2484 : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url, 2485 br.display, null))); 2486 if (ved.getBinding().hasStrength()) { 2487 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null))); 2488 c.getPieces() 2489 .add(checkForNoChange(ved.getBinding(), 2490 gen.new Piece(corePath + "terminologies.html#" + ved.getBinding().getStrength().toCode(), 2491 egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition()))); 2492 c.getPieces().add(gen.new Piece(null, ")", null)); 2493 } 2494 } 2495 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null)); 2496 r.getCells().add(c); 2497 2498 try { 2499 return gen.generate(model, corePath, 0, outputTracker); 2500 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2501 throw new FHIRException(e.getMessage(), e); 2502 } 2503 } 2504 2505 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 2506 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 2507 while (i < ed.getSnapshot().getElement().size() 2508 && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".")) { 2509 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath() + ".url")) 2510 return ed.getSnapshot().getElement().get(i); 2511 i++; 2512 } 2513 return null; 2514 } 2515 2516 private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) { 2517 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 2518 while (i < ed.getSnapshot().getElement().size() 2519 && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".")) { 2520 if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".value")) 2521 return ed.getSnapshot().getElement().get(i); 2522 i++; 2523 } 2524 return null; 2525 } 2526 2527 private static final int AGG_NONE = 0; 2528 private static final int AGG_IND = 1; 2529 private static final int AGG_GR = 2; 2530 private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false; 2531 2532 private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, 2533 StructureDefinition profile, String corePath, String imagePath) { 2534 Cell c = gen.new Cell(); 2535 r.getCells().add(c); 2536 List<TypeRefComponent> types = e.getType(); 2537 if (!e.hasType()) { 2538 if (e.hasContentReference()) { 2539 return c; 2540 } else { 2541 ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER); 2542 if (d != null && d.hasType()) { 2543 types = new ArrayList<ElementDefinition.TypeRefComponent>(); 2544 for (TypeRefComponent tr : d.getType()) { 2545 TypeRefComponent tt = tr.copy(); 2546 tt.setUserData(DERIVATION_EQUALS, true); 2547 types.add(tt); 2548 } 2549 } else 2550 return c; 2551 } 2552 } 2553 2554 boolean first = true; 2555 2556 TypeRefComponent tl = null; 2557 for (TypeRefComponent t : types) { 2558 if (first) 2559 first = false; 2560 else 2561 c.addPiece(checkForNoChange(tl, gen.new Piece(null, ", ", null))); 2562 tl = t; 2563 if (t.hasTarget()) { 2564 c.getPieces().add(gen.new Piece(corePath + "references.html", t.getWorkingCode(), null)); 2565 c.getPieces().add(gen.new Piece(null, "(", null)); 2566 boolean tfirst = true; 2567 for (UriType u : t.getTargetProfile()) { 2568 if (tfirst) 2569 tfirst = false; 2570 else 2571 c.addPiece(gen.new Piece(null, " | ", null)); 2572 genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue()); 2573 } 2574 c.getPieces().add(gen.new Piece(null, ")", null)); 2575 if (t.getAggregation().size() > 0) { 2576 c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", " {", null)); 2577 boolean firstA = true; 2578 for (Enumeration<AggregationMode> a : t.getAggregation()) { 2579 if (firstA = true) 2580 firstA = false; 2581 else 2582 c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", ", ", null)); 2583 c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", 2584 codeForAggregation(a.getValue()), hintForAggregation(a.getValue()))); 2585 } 2586 c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", "}", null)); 2587 } 2588 } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a 2589 // profiled 2590 // type 2591 String ref; 2592 ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue()); 2593 if (ref != null) { 2594 String[] parts = ref.split("\\|"); 2595 if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) { 2596// c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd 2597 c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode()))); 2598 } else { 2599// c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode()))); 2600 c.addPiece(checkForNoChange(t, 2601 gen.new Piece( 2602 (t.getProfile().get(0).getValue().startsWith(corePath + "StructureDefinition") ? corePath : "") 2603 + parts[0], 2604 parts[1], t.getWorkingCode()))); 2605 } 2606 } else 2607 c.addPiece(checkForNoChange(t, 2608 gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath) ? corePath : "") + ref, 2609 t.getWorkingCode(), null))); 2610 } else { 2611 String tc = t.getWorkingCode(); 2612 if (pkp != null && pkp.hasLinkFor(tc)) { 2613 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null))); 2614 } else 2615 c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null))); 2616 } 2617 } 2618 return c; 2619 } 2620 2621 public void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, 2622 TypeRefComponent t, String u) { 2623 if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2624 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u); 2625 if (sd != null) { 2626 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 2627 c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null))); 2628 } else { 2629 String rn = u.substring(40); 2630 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null))); 2631 } 2632 } else if (Utilities.isAbsoluteUrl(u)) { 2633 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u); 2634 if (sd != null) { 2635 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 2636 String ref = pkp.getLinkForProfile(null, sd.getUrl()); 2637 if (ref.contains("|")) 2638 ref = ref.substring(0, ref.indexOf("|")); 2639 c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null))); 2640 } else 2641 c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null))); 2642 } else if (t.hasTargetProfile() && u.startsWith("#")) 2643 c.addPiece(checkForNoChange(t, 2644 gen.new Piece(corePath + profileBaseFileName + "." + u.substring(1).toLowerCase() + ".html", u, null))); 2645 } 2646 2647 private boolean isProfiledType(List<CanonicalType> theProfile) { 2648 for (CanonicalType next : theProfile) { 2649 if (StringUtils.defaultString(next.getValueAsString()).contains(":")) { 2650 return true; 2651 } 2652 } 2653 return false; 2654 } 2655 2656 private String codeForAggregation(AggregationMode a) { 2657 switch (a) { 2658 case BUNDLED: 2659 return "b"; 2660 case CONTAINED: 2661 return "c"; 2662 case REFERENCED: 2663 return "r"; 2664 default: 2665 return "?"; 2666 } 2667 } 2668 2669 private String hintForAggregation(AggregationMode a) { 2670 if (a != null) 2671 return a.getDefinition(); 2672 else 2673 return null; 2674 } 2675 2676 private String checkPrepend(String corePath, String path) { 2677 if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:"))) 2678 return corePath + path; 2679 else 2680 return path; 2681 } 2682 2683 private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) { 2684 for (ElementDefinition ed : elements) 2685 if (ed.hasSliceName() && ("#" + ed.getSliceName()).equals(contentReference)) 2686 return ed; 2687 return null; 2688 } 2689 2690 private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) { 2691 for (ElementDefinition ed : elements) 2692 if (ed.hasId() && ("#" + ed.getId()).equals(contentReference)) 2693 return ed; 2694 return null; 2695 } 2696 2697 public static String describeExtensionContext(StructureDefinition ext) { 2698 StringBuilder b = new StringBuilder(); 2699 b.append("Use on "); 2700 for (int i = 0; i < ext.getContext().size(); i++) { 2701 StructureDefinitionContextComponent ec = ext.getContext().get(i); 2702 if (i > 0) 2703 b.append(i < ext.getContext().size() - 1 ? ", " : " or "); 2704 b.append(ec.getType().getDisplay()); 2705 b.append(" "); 2706 b.append(ec.getExpression()); 2707 } 2708 if (ext.hasContextInvariant()) { 2709 b.append( 2710 ", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = "); 2711 boolean first = true; 2712 for (StringType s : ext.getContextInvariant()) { 2713 if (first) 2714 first = false; 2715 else 2716 b.append(", "); 2717 b.append("<code>" + s.getValue() + "</code>"); 2718 } 2719 } 2720 return b.toString(); 2721 } 2722 2723 private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) { 2724 IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 2725 StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 2726 if (min.isEmpty() && fallback != null) 2727 min = fallback.getMinElement(); 2728 if (max.isEmpty() && fallback != null) 2729 max = fallback.getMaxElement(); 2730 2731 tracker.used = !max.isEmpty() && !max.getValue().equals("0"); 2732 2733 if (min.isEmpty() && max.isEmpty()) 2734 return null; 2735 else 2736 return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue()); 2737 } 2738 2739 private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, 2740 UnusedTracker tracker, ElementDefinition fallback) { 2741 IntegerType min = !hasDef ? new IntegerType() 2742 : definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 2743 StringType max = !hasDef ? new StringType() 2744 : definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 2745 if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 2746 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 2747 if (base.hasMinElement()) { 2748 min = base.getMinElement().copy(); 2749 min.setUserData(DERIVATION_EQUALS, true); 2750 } 2751 } 2752 if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 2753 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 2754 if (base.hasMaxElement()) { 2755 max = base.getMaxElement().copy(); 2756 max.setUserData(DERIVATION_EQUALS, true); 2757 } 2758 } 2759 if (min.isEmpty() && fallback != null) 2760 min = fallback.getMinElement(); 2761 if (max.isEmpty() && fallback != null) 2762 max = fallback.getMaxElement(); 2763 2764 if (!max.isEmpty()) 2765 tracker.used = !max.getValue().equals("0"); 2766 2767 Cell cell = gen.new Cell(null, null, null, null, null); 2768 row.getCells().add(cell); 2769 if (!min.isEmpty() || !max.isEmpty()) { 2770 cell.addPiece( 2771 checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null))); 2772 cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null))); 2773 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null))); 2774 } 2775 } 2776 2777 private Piece checkForNoChange(Element source, Piece piece) { 2778 if (source.hasUserData(DERIVATION_EQUALS)) { 2779 piece.addStyle("opacity: 0.4"); 2780 } 2781 return piece; 2782 } 2783 2784 private Piece checkForNoChange(Element src1, Element src2, Piece piece) { 2785 if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) { 2786 piece.addStyle("opacity: 0.5"); 2787 } 2788 return piece; 2789 } 2790 2791 public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, 2792 boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, 2793 boolean logicalModel, boolean allInvariants, Set<String> outputTracker) throws IOException, FHIRException { 2794 assert (diff != snapshot);// check it's ok to get rid of one of these 2795 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics, true); 2796 TableModel model = gen.initNormalTable(corePath, false, true, profile.getId() + (diff ? "d" : "s"), false, 2797 TableGenerationMode.XML); 2798 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 2799 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2800 profiles.add(profile); 2801 if (list.isEmpty()) { 2802 ElementDefinition root = new ElementDefinition().setPath(profile.getType()); 2803 root.setId(profile.getType()); 2804 list.add(root); 2805 } 2806 genElement(defFile == null ? null : defFile + "#", gen, model.getRows(), list.get(0), list, profiles, diff, 2807 profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, 2808 profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants, null); 2809 try { 2810 return gen.generate(model, imagePath, 0, outputTracker); 2811 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2812 throw new FHIRException("Error generating table for profile " + profile.getUrl() + ": " + e.getMessage(), e); 2813 } 2814 } 2815 2816 public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, 2817 String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) 2818 throws IOException, FHIRException { 2819 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics, true); 2820 TableModel model = gen.initGridTable(corePath, profile.getId()); 2821 List<ElementDefinition> list = profile.getSnapshot().getElement(); 2822 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2823 profiles.add(profile); 2824 genGridElement(defFile == null ? null : defFile + "#", gen, model.getRows(), list.get(0), list, profiles, true, 2825 profileBaseFileName, null, corePath, imagePath, true, 2826 profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list)); 2827 try { 2828 return gen.generate(model, imagePath, 1, outputTracker); 2829 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2830 throw new FHIRException(e.getMessage(), e); 2831 } 2832 } 2833 2834 private boolean usesMustSupport(List<ElementDefinition> list) { 2835 for (ElementDefinition ed : list) 2836 if (ed.hasMustSupport() && ed.getMustSupport()) 2837 return true; 2838 return false; 2839 } 2840 2841 private Row genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, 2842 List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, 2843 Boolean extensions, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, 2844 boolean isConstraintMode, boolean allInvariants, Row slicingRow) throws IOException, FHIRException { 2845 Row originalRow = slicingRow; 2846 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size() - 1); 2847 String s = tail(element.getPath()); 2848 if (element.hasSliceName()) 2849 s = s + ":" + element.getSliceName(); 2850 Row typesRow = null; 2851 2852 List<ElementDefinition> children = getChildren(all, element); 2853 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 2854// if (!snapshot && isExtension && extensions != null && extensions != isExtension) 2855// return; 2856 2857 if (!onlyInformationIsMapping(all, element)) { 2858 Row row = gen.new Row(); 2859 row.setAnchor(element.getPath()); 2860 row.setColor(getRowColor(element, isConstraintMode)); 2861 if (element.hasSlicing()) 2862 row.setLineColor(1); 2863 else if (element.hasSliceName()) 2864 row.setLineColor(2); 2865 else 2866 row.setLineColor(0); 2867 boolean hasDef = element != null; 2868 boolean ext = false; 2869 if (tail(element.getPath()).equals("extension")) { 2870 if (element.hasType() && element.getType().get(0).hasProfile() 2871 && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 2872 row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2873 else 2874 row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2875 ext = true; 2876 } else if (tail(element.getPath()).equals("modifierExtension")) { 2877 if (element.hasType() && element.getType().get(0).hasProfile() 2878 && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 2879 row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2880 else 2881 row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2882 } else if (!hasDef || element.getType().size() == 0) 2883 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 2884 else if (hasDef && element.getType().size() > 1) { 2885 if (allAreReference(element.getType())) 2886 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2887 else { 2888 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 2889 typesRow = row; 2890 } 2891 } else if (hasDef && element.getType().get(0).getWorkingCode() != null 2892 && element.getType().get(0).getWorkingCode().startsWith("@")) 2893 row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE); 2894 else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode())) 2895 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 2896 else if (hasDef && element.getType().get(0).hasTarget()) 2897 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2898 else if (hasDef && isDataType(element.getType().get(0).getWorkingCode())) 2899 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2900 else 2901 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 2902 String ref = defPath == null ? null : defPath + element.getId(); 2903 UnusedTracker used = new UnusedTracker(); 2904 used.used = true; 2905 if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR)) 2906 s = "@" + s; 2907 Cell left = gen.new Cell(null, ref, s, 2908 (element.hasSliceName() ? translate("sd.table", "Slice") + " " + element.getSliceName() : "") 2909 + (hasDef && element.hasSliceName() ? ": " : "") + (!hasDef ? null : gt(element.getDefinitionElement())), 2910 null); 2911 row.getCells().add(left); 2912 Cell gc = gen.new Cell(); 2913 row.getCells().add(gc); 2914 if (element != null && element.getIsModifier()) 2915 checkForNoChange(element.getIsModifierElement(), gc 2916 .addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false)); 2917 if (element != null && element.getMustSupport()) 2918 checkForNoChange(element.getMustSupportElement(), gc 2919 .addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false)); 2920 if (element != null && element.getIsSummary()) 2921 checkForNoChange(element.getIsSummaryElement(), gc.addStyledText( 2922 translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false)); 2923 if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty())) 2924 gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null, 2925 null, false); 2926 2927 ExtensionContext extDefn = null; 2928 if (ext) { 2929 if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) { 2930 String eurl = element.getType().get(0).getProfile().get(0).getValue(); 2931 extDefn = locateExtension(StructureDefinition.class, eurl); 2932 if (extDefn == null) { 2933 genCardinality(gen, element, row, hasDef, used, null); 2934 row.getCells().add(gen.new Cell(null, null, "?? " + element.getType().get(0).getProfile(), null, null)); 2935 generateDescription(gen, row, element, null, used.used, profile.getUrl(), eurl, profile, corePath, 2936 imagePath, root, logicalModel, allInvariants, snapshot); 2937 } else { 2938 String name = urltail(eurl); 2939 left.getPieces().get(0).setText(name); 2940 // left.getPieces().get(0).setReference((String) 2941 // extDefn.getExtensionStructure().getTag("filename")); 2942 left.getPieces().get(0).setHint(translate("sd.table", "Extension URL") + " = " + extDefn.getUrl()); 2943 genCardinality(gen, element, row, hasDef, used, extDefn.getElement()); 2944 ElementDefinition valueDefn = extDefn.getExtensionValueDefinition(); 2945 if (valueDefn != null && !"0".equals(valueDefn.getMax())) 2946 genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath); 2947 else // if it's complex, we just call it nothing 2948 // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), 2949 // profileBaseFileName, profile); 2950 row.getCells().add(gen.new Cell(null, null, "(" + translate("sd.table", "Complex") + ")", null, null)); 2951 generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, 2952 corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot); 2953 } 2954 } else { 2955 genCardinality(gen, element, row, hasDef, used, null); 2956 if ("0".equals(element.getMax())) 2957 row.getCells().add(gen.new Cell()); 2958 else 2959 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2960 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, 2961 logicalModel, allInvariants, snapshot); 2962 } 2963 } else { 2964 genCardinality(gen, element, row, hasDef, used, null); 2965 if (element.hasSlicing()) 2966 row.getCells().add(gen.new Cell(null, corePath + "profiling.html#slicing", "(Slice Definition)", null, null)); 2967 else if (hasDef && !"0".equals(element.getMax()) && typesRow == null) 2968 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2969 else 2970 row.getCells().add(gen.new Cell()); 2971 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, 2972 logicalModel, allInvariants, snapshot); 2973 } 2974 if (element.hasSlicing()) { 2975 if (standardExtensionSlicing(element)) { 2976 used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing 2977 // ... element.hasType() && element.getType().get(0).hasProfile(); 2978 showMissing = false; // ? 2979 } else { 2980 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 2981 slicingRow = row; 2982 for (Cell cell : row.getCells()) 2983 for (Piece p : cell.getPieces()) { 2984 p.addStyle("font-style: italic"); 2985 } 2986 } 2987 } else if (element.hasSliceName()) { 2988 row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM); 2989 } 2990 if (used.used || showMissing) 2991 rows.add(row); 2992 if (!used.used && !element.hasSlicing()) { 2993 for (Cell cell : row.getCells()) 2994 for (Piece p : cell.getPieces()) { 2995 p.setStyle("text-decoration:line-through"); 2996 p.setReference(null); 2997 } 2998 } else { 2999 if (slicingRow != originalRow && !children.isEmpty()) { 3000 // we've entered a slice; we're going to create a holder row for the slice 3001 // children 3002 Row hrow = gen.new Row(); 3003 hrow.setAnchor(element.getPath()); 3004 hrow.setColor(getRowColor(element, isConstraintMode)); 3005 hrow.setLineColor(1); 3006 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 3007 hrow.getCells().add(gen.new Cell(null, null, "(All Slices)", "", null)); 3008 hrow.getCells().add(gen.new Cell()); 3009 hrow.getCells().add(gen.new Cell()); 3010 hrow.getCells().add(gen.new Cell()); 3011 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null)); 3012 row.getSubRows().add(hrow); 3013 row = hrow; 3014 } 3015 if (typesRow != null && !children.isEmpty()) { 3016 // we've entered a typing slice; we're going to create a holder row for the all 3017 // types children 3018 Row hrow = gen.new Row(); 3019 hrow.setAnchor(element.getPath()); 3020 hrow.setColor(getRowColor(element, isConstraintMode)); 3021 hrow.setLineColor(1); 3022 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 3023 hrow.getCells().add(gen.new Cell(null, null, "(All Types)", "", null)); 3024 hrow.getCells().add(gen.new Cell()); 3025 hrow.getCells().add(gen.new Cell()); 3026 hrow.getCells().add(gen.new Cell()); 3027 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null)); 3028 row.getSubRows().add(hrow); 3029 row = hrow; 3030 } 3031 3032 Row currRow = row; 3033 for (ElementDefinition child : children) { 3034 if (!child.hasSliceName()) 3035 currRow = row; 3036 if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) 3037 && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) 3038 currRow = genElement(defPath, gen, currRow.getSubRows(), child, all, profiles, showMissing, 3039 profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, 3040 allInvariants, currRow); 3041 } 3042// if (!snapshot && (extensions == null || !extensions)) 3043// for (ElementDefinition child : children) 3044// if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension")) 3045// genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants); 3046 } 3047 if (typesRow != null) { 3048 makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName); 3049 } 3050 } 3051 return slicingRow; 3052 } 3053 3054 private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, 3055 String corePath, String profileBaseFileName) { 3056 // create a child for each choice 3057 for (TypeRefComponent tr : element.getType()) { 3058 Row choicerow = gen.new Row(); 3059 String t = tr.getWorkingCode(); 3060 if (isReference(t)) { 3061 choicerow.getCells() 3062 .add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null)); 3063 choicerow.getCells().add(gen.new Cell()); 3064 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 3065 choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 3066 Cell c = gen.new Cell(); 3067 choicerow.getCells().add(c); 3068 if (ADD_REFERENCE_TO_TABLE) { 3069 if (tr.getWorkingCode().equals("canonical")) 3070 c.getPieces().add(gen.new Piece(corePath + "datatypes.html#canonical", "canonical", null)); 3071 else 3072 c.getPieces().add(gen.new Piece(corePath + "references.html#Reference", "Reference", null)); 3073 c.getPieces().add(gen.new Piece(null, "(", null)); 3074 } 3075 boolean first = true; 3076 for (CanonicalType rt : tr.getTargetProfile()) { 3077 if (!first) 3078 c.getPieces().add(gen.new Piece(null, " | ", null)); 3079 genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue()); 3080 first = false; 3081 } 3082 if (ADD_REFERENCE_TO_TABLE) 3083 c.getPieces().add(gen.new Piece(null, ")", null)); 3084 3085 } else { 3086 StructureDefinition sd = context.fetchTypeDefinition(t); 3087 if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 3088 choicerow.getCells().add(gen.new Cell(null, null, 3089 tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 3090 choicerow.getCells().add(gen.new Cell()); 3091 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 3092 choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 3093 choicerow.getCells().add(gen.new Cell(null, corePath + "datatypes.html#" + t, t, null, null)); 3094 // } else if (definitions.getConstraints().contthnsKey(t)) { 3095 // ProfiledType pt = definitions.getConstraints().get(t); 3096 // choicerow.getCells().add(gen.new Cell(null, null, e.getName().replace("[x]", 3097 // Utilities.capitalize(pt.getBaseType())), 3098 // definitions.getTypes().containsKey(t) ? 3099 // definitions.getTypes().get(t).getDefinition() : null, null)); 3100 // choicerow.getCells().add(gen.new Cell()); 3101 // choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 3102 // choicerow.setIcon("icon_datatype.gif", 3103 // HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 3104 // choicerow.getCells().add(gen.new Cell(null, 3105 // definitions.getSrcFile(t)+".html#"+t.replace("*", "open"), t, null, null)); 3106 } else { 3107 choicerow.getCells().add(gen.new Cell(null, null, 3108 tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 3109 choicerow.getCells().add(gen.new Cell()); 3110 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 3111 choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 3112 choicerow.getCells().add(gen.new Cell(null, pkp.getLinkFor(corePath, t), t, null, null)); 3113 } 3114 } 3115 choicerow.getCells().add(gen.new Cell()); 3116 subRows.add(choicerow); 3117 } 3118 } 3119 3120 private boolean isReference(String t) { 3121 return t.equals("Reference") || t.equals("canonical"); 3122 } 3123 3124 private void genGridElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, 3125 List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, 3126 Boolean extensions, String corePath, String imagePath, boolean root, boolean isConstraintMode) 3127 throws IOException, FHIRException { 3128 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size() - 1); 3129 String s = tail(element.getPath()); 3130 List<ElementDefinition> children = getChildren(all, element); 3131 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 3132 3133 if (!onlyInformationIsMapping(all, element)) { 3134 Row row = gen.new Row(); 3135 row.setAnchor(element.getPath()); 3136 row.setColor(getRowColor(element, isConstraintMode)); 3137 if (element.hasSlicing()) 3138 row.setLineColor(1); 3139 else if (element.hasSliceName()) 3140 row.setLineColor(2); 3141 else 3142 row.setLineColor(0); 3143 boolean hasDef = element != null; 3144 String ref = defPath == null ? null : defPath + element.getId(); 3145 UnusedTracker used = new UnusedTracker(); 3146 used.used = true; 3147 Cell left = gen.new Cell(); 3148 if (element.getType().size() == 1 && element.getType().get(0).isPrimitive()) 3149 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())) 3150 .addStyle("font-weight:bold")); 3151 else 3152 left.getPieces() 3153 .add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement()))); 3154 if (element.hasSliceName()) { 3155 left.getPieces().add(gen.new Piece("br")); 3156 String indent = StringUtils.repeat('\u00A0', 1 + 2 * (element.getPath().split("\\.").length)); 3157 left.getPieces().add(gen.new Piece(null, indent + "(" + element.getSliceName() + ")", null)); 3158 } 3159 row.getCells().add(left); 3160 3161 ExtensionContext extDefn = null; 3162 genCardinality(gen, element, row, hasDef, used, null); 3163 if (hasDef && !"0".equals(element.getMax())) 3164 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 3165 else 3166 row.getCells().add(gen.new Cell()); 3167 generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null); 3168 /* 3169 * if (element.hasSlicing()) { if (standardExtensionSlicing(element)) { 3170 * used.used = element.hasType() && element.getType().get(0).hasProfile(); 3171 * showMissing = false; } else { row.setIcon("icon_slice.png", 3172 * HierarchicalTableGenerator.TEXT_ICON_SLICE); 3173 * row.getCells().get(2).getPieces().clear(); for (Cell cell : row.getCells()) 3174 * for (Piece p : cell.getPieces()) { p.addStyle("font-style: italic"); } } } 3175 */ 3176 rows.add(row); 3177 for (ElementDefinition child : children) 3178 if (child.getMustSupport()) 3179 genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, 3180 isExtension, corePath, imagePath, false, isConstraintMode); 3181 } 3182 } 3183 3184 private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value) { 3185 if (value.contains("#")) { 3186 StructureDefinition ext = context.fetchResource(StructureDefinition.class, 3187 value.substring(0, value.indexOf("#"))); 3188 if (ext == null) 3189 return null; 3190 String tail = value.substring(value.indexOf("#") + 1); 3191 ElementDefinition ed = null; 3192 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 3193 if (tail.equals(ted.getSliceName())) { 3194 ed = ted; 3195 return new ExtensionContext(ext, ed); 3196 } 3197 } 3198 return null; 3199 } else { 3200 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 3201 if (ext == null) 3202 return null; 3203 else 3204 return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0)); 3205 } 3206 } 3207 3208 private boolean extensionIsComplex(String value) { 3209 if (value.contains("#")) { 3210 StructureDefinition ext = context.fetchResource(StructureDefinition.class, 3211 value.substring(0, value.indexOf("#"))); 3212 if (ext == null) 3213 return false; 3214 String tail = value.substring(value.indexOf("#") + 1); 3215 ElementDefinition ed = null; 3216 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 3217 if (tail.equals(ted.getSliceName())) { 3218 ed = ted; 3219 break; 3220 } 3221 } 3222 if (ed == null) 3223 return false; 3224 int i = ext.getSnapshot().getElement().indexOf(ed); 3225 int j = i + 1; 3226 while (j < ext.getSnapshot().getElement().size() 3227 && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath())) 3228 j++; 3229 return j - i > 5; 3230 } else { 3231 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 3232 return ext != null && ext.getSnapshot().getElement().size() > 5; 3233 } 3234 } 3235 3236 private String getRowColor(ElementDefinition element, boolean isConstraintMode) { 3237 switch (element.getUserInt(UD_ERROR_STATUS)) { 3238 case STATUS_HINT: 3239 return ROW_COLOR_HINT; 3240 case STATUS_WARNING: 3241 return ROW_COLOR_WARNING; 3242 case STATUS_ERROR: 3243 return ROW_COLOR_ERROR; 3244 case STATUS_FATAL: 3245 return ROW_COLOR_FATAL; 3246 } 3247 if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains(".")) 3248 return null; // ROW_COLOR_NOT_MUST_SUPPORT; 3249 else 3250 return null; 3251 } 3252 3253 private String urltail(String path) { 3254 if (path.contains("#")) 3255 return path.substring(path.lastIndexOf('#') + 1); 3256 if (path.contains("/")) 3257 return path.substring(path.lastIndexOf('/') + 1); 3258 else 3259 return path; 3260 3261 } 3262 3263 private boolean standardExtensionSlicing(ElementDefinition element) { 3264 String t = tail(element.getPath()); 3265 return (t.equals("extension") || t.equals("modifierExtension")) 3266 && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 3267 && element.getSlicing().getDiscriminator().get(0).getPath().equals("url") 3268 && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE); 3269 } 3270 3271 private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, 3272 ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, 3273 String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, boolean snapshot) 3274 throws IOException, FHIRException { 3275 return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, 3276 logicalModel, allInvariants, null, snapshot); 3277 } 3278 3279 private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, 3280 ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, 3281 String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, 3282 ElementDefinition valueDefn, boolean snapshot) throws IOException, FHIRException { 3283 Cell c = gen.new Cell(); 3284 row.getCells().add(c); 3285 3286 if (used) { 3287 if (logicalModel && ToolingExtensions.hasExtension(profile, 3288 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) { 3289 if (root) { 3290 c.getPieces().add( 3291 gen.new Piece(null, translate("sd.table", "XML Namespace") + ": ", null).addStyle("font-weight:bold")); 3292 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, 3293 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 3294 } else if (!root 3295 && ToolingExtensions.hasExtension(definition, 3296 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") 3297 && !ToolingExtensions 3298 .readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") 3299 .equals(ToolingExtensions.readStringExtension(profile, 3300 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) { 3301 c.getPieces().add( 3302 gen.new Piece(null, translate("sd.table", "XML Namespace") + ": ", null).addStyle("font-weight:bold")); 3303 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, 3304 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 3305 } 3306 } 3307 3308 if (definition.hasContentReference()) { 3309 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference()); 3310 if (ed == null) 3311 c.getPieces().add(gen.new Piece(null, 3312 translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null)); 3313 else 3314 c.getPieces().add(gen.new Piece("#" + ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null)); 3315 } 3316 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 3317 c.getPieces().add(checkForNoChange(definition.getFixed(), 3318 gen.new Piece(null, "\"" + buildJson(definition.getFixed()) + "\"", null).addStyle("color: darkgreen"))); 3319 } else { 3320 if (definition != null && definition.hasShort()) { 3321 if (!c.getPieces().isEmpty()) 3322 c.addPiece(gen.new Piece("br")); 3323 c.addPiece(checkForNoChange(definition.getShortElement(), 3324 gen.new Piece(null, gt(definition.getShortElement()), null))); 3325 } else if (fallback != null && fallback.hasShort()) { 3326 if (!c.getPieces().isEmpty()) 3327 c.addPiece(gen.new Piece("br")); 3328 c.addPiece( 3329 checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null))); 3330 } 3331 if (url != null) { 3332 if (!c.getPieces().isEmpty()) 3333 c.addPiece(gen.new Piece("br")); 3334 String fullUrl = url.startsWith("#") ? baseURL + url : url; 3335 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 3336 String ref = null; 3337 String ref2 = null; 3338 String fixedUrl = null; 3339 if (ed != null) { 3340 String p = ed.getUserString("path"); 3341 if (p != null) { 3342 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 3343 } 3344 fixedUrl = getFixedUrl(ed); 3345 if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension? 3346 if (fixedUrl.equals(url)) 3347 fixedUrl = null; 3348 else { 3349 StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl); 3350 if (ed2 != null) { 3351 String p2 = ed2.getUserString("path"); 3352 if (p2 != null) { 3353 ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2); 3354 } 3355 } 3356 } 3357 } 3358 } 3359 if (fixedUrl == null) { 3360 c.getPieces() 3361 .add(gen.new Piece(null, translate("sd.table", "URL") + ": ", null).addStyle("font-weight:bold")); 3362 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3363 } else { 3364 // reference to a profile take on the extension show the base URL 3365 c.getPieces() 3366 .add(gen.new Piece(null, translate("sd.table", "URL") + ": ", null).addStyle("font-weight:bold")); 3367 c.getPieces().add(gen.new Piece(ref2, fixedUrl, null)); 3368 c.getPieces().add( 3369 gen.new Piece(null, translate("sd.table", " profiled by ") + " ", null).addStyle("font-weight:bold")); 3370 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3371 3372 } 3373 } 3374 3375 if (definition.hasSlicing()) { 3376 if (!c.getPieces().isEmpty()) 3377 c.addPiece(gen.new Piece("br")); 3378 c.getPieces() 3379 .add(gen.new Piece(null, translate("sd.table", "Slice") + ": ", null).addStyle("font-weight:bold")); 3380 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 3381 } 3382 if (definition != null) { 3383 ElementDefinitionBindingComponent binding = null; 3384 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 3385 binding = valueDefn.getBinding(); 3386 else if (definition.hasBinding()) 3387 binding = definition.getBinding(); 3388 if (binding != null && !binding.isEmpty()) { 3389 if (!c.getPieces().isEmpty()) 3390 c.addPiece(gen.new Piece("br")); 3391 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 3392 c.getPieces().add(checkForNoChange(binding, 3393 gen.new Piece(null, translate("sd.table", "Binding") + ": ", null).addStyle("font-weight:bold"))); 3394 c.getPieces() 3395 .add(checkForNoChange(binding, 3396 gen.new Piece( 3397 br.url == null ? null 3398 : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url, 3399 br.display, null))); 3400 if (binding.hasStrength()) { 3401 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 3402 c.getPieces() 3403 .add(checkForNoChange(binding, 3404 gen.new Piece(corePath + "terminologies.html#" + binding.getStrength().toCode(), 3405 egt(binding.getStrengthElement()), binding.getStrength().getDefinition()))); 3406 3407 c.getPieces().add(gen.new Piece(null, ")", null)); 3408 } 3409 if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) { 3410 br = pkp.resolveBinding(profile, 3411 ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), 3412 definition.getPath()); 3413 c.addPiece(gen.new Piece("br")); 3414 c.getPieces() 3415 .add(checkForNoChange(binding, 3416 gen.new Piece(corePath + "extension-elementdefinition-maxvalueset.html", 3417 translate("sd.table", "Max Binding") + ": ", "Max Value Set Extension") 3418 .addStyle("font-weight:bold"))); 3419 c.getPieces() 3420 .add(checkForNoChange(binding, 3421 gen.new Piece( 3422 br.url == null ? null 3423 : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url, 3424 br.display, null))); 3425 } 3426 if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) { 3427 br = pkp.resolveBinding(profile, 3428 ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), 3429 definition.getPath()); 3430 c.addPiece(gen.new Piece("br")); 3431 c.getPieces() 3432 .add(checkForNoChange(binding, 3433 gen.new Piece(corePath + "extension-elementdefinition-minvalueset.html", 3434 translate("sd.table", "Min Binding") + ": ", "Min Value Set Extension") 3435 .addStyle("font-weight:bold"))); 3436 c.getPieces() 3437 .add(checkForNoChange(binding, 3438 gen.new Piece( 3439 br.url == null ? null 3440 : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url, 3441 br.display, null))); 3442 } 3443 } 3444 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 3445 if (!inv.hasSource() || allInvariants) { 3446 if (!c.getPieces().isEmpty()) 3447 c.addPiece(gen.new Piece("br")); 3448 c.getPieces().add( 3449 checkForNoChange(inv, gen.new Piece(null, inv.getKey() + ": ", null).addStyle("font-weight:bold"))); 3450 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null))); 3451 } 3452 } 3453 if ((definition.hasBase() && definition.getBase().getMax().equals("*")) 3454 || (definition.hasMax() && definition.getMax().equals("*"))) { 3455 if (c.getPieces().size() > 0) 3456 c.addPiece(gen.new Piece("br")); 3457 if (definition.hasOrderMeaning()) { 3458 c.getPieces() 3459 .add(gen.new Piece(null, "This repeating element order: " + definition.getOrderMeaning(), null)); 3460 } else { 3461 // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, 3462 // "This repeating element has no defined order", null)); 3463 } 3464 } 3465 3466 if (definition.hasFixed()) { 3467 if (!c.getPieces().isEmpty()) 3468 c.addPiece(gen.new Piece("br")); 3469 c.getPieces().add(checkForNoChange(definition.getFixed(), 3470 gen.new Piece(null, translate("sd.table", "Fixed Value") + ": ", null).addStyle("font-weight:bold"))); 3471 if (!useTableForFixedValues || definition.getFixed().isPrimitive()) { 3472 String s = buildJson(definition.getFixed()); 3473 String link = null; 3474 if (Utilities.isAbsoluteUrl(s)) 3475 link = pkp.getLinkForUrl(corePath, s); 3476 c.getPieces().add( 3477 checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 3478 } else { 3479 c.getPieces().add(checkForNoChange(definition.getFixed(), 3480 gen.new Piece(null, "As shown", null).addStyle("color: darkgreen"))); 3481 genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath); 3482 } 3483 if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) { 3484 Piece p = describeCoded(gen, definition.getFixed()); 3485 if (p != null) 3486 c.getPieces().add(p); 3487 } 3488 } else if (definition.hasPattern()) { 3489 if (!c.getPieces().isEmpty()) 3490 c.addPiece(gen.new Piece("br")); 3491 c.getPieces() 3492 .add(checkForNoChange(definition.getPattern(), 3493 gen.new Piece(null, translate("sd.table", "Required Pattern") + ": ", null) 3494 .addStyle("font-weight:bold"))); 3495 if (!useTableForFixedValues || definition.getPattern().isPrimitive()) 3496 c.getPieces().add(checkForNoChange(definition.getPattern(), 3497 gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 3498 else { 3499 c.getPieces().add(checkForNoChange(definition.getPattern(), 3500 gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen"))); 3501 genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath); 3502 } 3503 } else if (definition.hasExample()) { 3504 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 3505 if (!c.getPieces().isEmpty()) 3506 c.addPiece(gen.new Piece("br")); 3507 c.getPieces() 3508 .add(checkForNoChange(ex, 3509 gen.new Piece(null, translate("sd.table", "Example") 3510 + ("".equals("General") ? "" : " " + ex.getLabel() + "'") + ": ", null) 3511 .addStyle("font-weight:bold"))); 3512 c.getPieces().add(checkForNoChange(ex, 3513 gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 3514 } 3515 } 3516 if (definition.hasMaxLength() && definition.getMaxLength() != 0) { 3517 if (!c.getPieces().isEmpty()) 3518 c.addPiece(gen.new Piece("br")); 3519 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), 3520 gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 3521 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), 3522 gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 3523 } 3524 if (profile != null) { 3525 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 3526 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 3527 ElementDefinitionMappingComponent map = null; 3528 for (ElementDefinitionMappingComponent m : definition.getMapping()) 3529 if (m.getIdentity().equals(md.getIdentity())) 3530 map = m; 3531 if (map != null) { 3532 for (int i = 0; i < definition.getMapping().size(); i++) { 3533 c.addPiece(gen.new Piece("br")); 3534 c.getPieces().add( 3535 gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME) 3536 + ": " + map.getMap(), null)); 3537 } 3538 } 3539 } 3540 } 3541 } 3542 } 3543 } 3544 } 3545 return c; 3546 } 3547 3548 private void genFixedValue(HierarchicalTableGenerator gen, Row erow, Type value, boolean snapshot, boolean pattern, 3549 String corePath) { 3550 String ref = pkp.getLinkFor(corePath, value.fhirType()); 3551 ref = ref.substring(0, ref.indexOf(".html")) + "-definitions.html#"; 3552 StructureDefinition sd = context.fetchTypeDefinition(value.fhirType()); 3553 3554 for (org.hl7.fhir.r4.model.Property t : value.children()) { 3555 if (t.getValues().size() > 0 || snapshot) { 3556 ElementDefinition ed = findElementDefinition(sd, t.getName()); 3557 if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) { 3558 Row row = gen.new Row(); 3559 erow.getSubRows().add(row); 3560 Cell c = gen.new Cell(); 3561 row.getCells().add(c); 3562 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref + ed.getPath() 3563 : corePath + "element-definitions.html#" + ed.getBase().getPath()), t.getName(), null)); 3564 c = gen.new Cell(); 3565 row.getCells().add(c); 3566 c.addPiece(gen.new Piece(null, null, null)); 3567 c = gen.new Cell(); 3568 row.getCells().add(c); 3569 if (!pattern) { 3570 c.addPiece(gen.new Piece(null, "0..0", null)); 3571 row.setIcon("icon_fixed.gif", "Fixed Value" /* HierarchicalTableGenerator.TEXT_ICON_FIXED */); 3572 } else if (isPrimitive(t.getTypeCode())) { 3573 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 3574 c.addPiece(gen.new Piece(null, 3575 "0.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 3576 } else if (isReference(t.getTypeCode())) { 3577 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 3578 c.addPiece(gen.new Piece(null, 3579 "0.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 3580 } else { 3581 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 3582 c.addPiece(gen.new Piece(null, 3583 "0.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 3584 } 3585 c = gen.new Cell(); 3586 row.getCells().add(c); 3587 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null)); 3588 c = gen.new Cell(); 3589 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3590 row.getCells().add(c); 3591 } else { 3592 for (Base b : t.getValues()) { 3593 Row row = gen.new Row(); 3594 erow.getSubRows().add(row); 3595 row.setIcon("icon_fixed.gif", "Fixed Value" /* HierarchicalTableGenerator.TEXT_ICON_FIXED */); 3596 3597 Cell c = gen.new Cell(); 3598 row.getCells().add(c); 3599 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref + ed.getPath() 3600 : corePath + "element-definitions.html#" + ed.getBase().getPath()), t.getName(), null)); 3601 3602 c = gen.new Cell(); 3603 row.getCells().add(c); 3604 c.addPiece(gen.new Piece(null, null, null)); 3605 3606 c = gen.new Cell(); 3607 row.getCells().add(c); 3608 if (pattern) 3609 c.addPiece(gen.new Piece(null, 3610 "1.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 3611 else 3612 c.addPiece(gen.new Piece(null, "1..1", null)); 3613 3614 c = gen.new Cell(); 3615 row.getCells().add(c); 3616 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, b.fhirType()), b.fhirType(), null)); 3617 3618 if (b.isPrimitive()) { 3619 c = gen.new Cell(); 3620 row.getCells().add(c); 3621 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3622 c.addPiece(gen.new Piece("br")); 3623 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 3624 String s = b.primitiveValue(); 3625 // ok. let's see if we can find a relevant link for this 3626 String link = null; 3627 if (Utilities.isAbsoluteUrl(s)) 3628 link = pkp.getLinkForUrl(corePath, s); 3629 c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen")); 3630 } else { 3631 c = gen.new Cell(); 3632 row.getCells().add(c); 3633 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3634 c.addPiece(gen.new Piece("br")); 3635 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 3636 c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen")); 3637 genFixedValue(gen, row, (Type) b, snapshot, pattern, corePath); 3638 } 3639 } 3640 } 3641 } 3642 } 3643 } 3644 3645 private ElementDefinition findElementDefinition(StructureDefinition sd, String name) { 3646 String path = sd.getType() + "." + name; 3647 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3648 if (ed.getPath().equals(path)) 3649 return ed; 3650 } 3651 throw new FHIRException("Unable to find element " + path); 3652 } 3653 3654 private String getFixedUrl(StructureDefinition sd) { 3655 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3656 if (ed.getPath().equals("Extension.url")) { 3657 if (ed.hasFixed() && ed.getFixed() instanceof UriType) 3658 return ed.getFixed().primitiveValue(); 3659 } 3660 } 3661 return null; 3662 } 3663 3664 private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) { 3665 if (fixed instanceof Coding) { 3666 Coding c = (Coding) fixed; 3667 ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), c.getDisplay()); 3668 if (vr.getDisplay() != null) 3669 return gen.new Piece(null, " (" + vr.getDisplay() + ")", null).addStyle("color: darkgreen"); 3670 } else if (fixed instanceof CodeableConcept) { 3671 CodeableConcept cc = (CodeableConcept) fixed; 3672 for (Coding c : cc.getCoding()) { 3673 ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), 3674 c.getDisplay()); 3675 if (vr.getDisplay() != null) 3676 return gen.new Piece(null, " (" + vr.getDisplay() + ")", null).addStyle("color: darkgreen"); 3677 } 3678 } 3679 return null; 3680 } 3681 3682 private boolean hasDescription(Type fixed) { 3683 if (fixed instanceof Coding) { 3684 return ((Coding) fixed).hasDisplay(); 3685 } else if (fixed instanceof CodeableConcept) { 3686 CodeableConcept cc = (CodeableConcept) fixed; 3687 if (cc.hasText()) 3688 return true; 3689 for (Coding c : cc.getCoding()) 3690 if (c.hasDisplay()) 3691 return true; 3692 } // (fixed instanceof CodeType) || (fixed instanceof Quantity); 3693 return false; 3694 } 3695 3696 private boolean isCoded(Type fixed) { 3697 return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) 3698 || (fixed instanceof Quantity); 3699 } 3700 3701 private Cell generateGridDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, 3702 ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, 3703 String corePath, String imagePath, boolean root, ElementDefinition valueDefn) throws IOException, FHIRException { 3704 Cell c = gen.new Cell(); 3705 row.getCells().add(c); 3706 3707 if (used) { 3708 if (definition.hasContentReference()) { 3709 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference()); 3710 if (ed == null) 3711 c.getPieces().add(gen.new Piece(null, "Unknown reference to " + definition.getContentReference(), null)); 3712 else 3713 c.getPieces().add(gen.new Piece("#" + ed.getPath(), "See " + ed.getPath(), null)); 3714 } 3715 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 3716 c.getPieces().add(checkForNoChange(definition.getFixed(), 3717 gen.new Piece(null, "\"" + buildJson(definition.getFixed()) + "\"", null).addStyle("color: darkgreen"))); 3718 } else { 3719 if (url != null) { 3720 if (!c.getPieces().isEmpty()) 3721 c.addPiece(gen.new Piece("br")); 3722 String fullUrl = url.startsWith("#") ? baseURL + url : url; 3723 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 3724 String ref = null; 3725 if (ed != null) { 3726 String p = ed.getUserString("path"); 3727 if (p != null) { 3728 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 3729 } 3730 } 3731 c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold")); 3732 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3733 } 3734 3735 if (definition.hasSlicing()) { 3736 if (!c.getPieces().isEmpty()) 3737 c.addPiece(gen.new Piece("br")); 3738 c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); 3739 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 3740 } 3741 if (definition != null) { 3742 ElementDefinitionBindingComponent binding = null; 3743 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 3744 binding = valueDefn.getBinding(); 3745 else if (definition.hasBinding()) 3746 binding = definition.getBinding(); 3747 if (binding != null && !binding.isEmpty()) { 3748 if (!c.getPieces().isEmpty()) 3749 c.addPiece(gen.new Piece("br")); 3750 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 3751 c.getPieces() 3752 .add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold"))); 3753 c.getPieces() 3754 .add(checkForNoChange(binding, 3755 gen.new Piece( 3756 br.url == null ? null 3757 : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url, 3758 br.display, null))); 3759 if (binding.hasStrength()) { 3760 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 3761 c.getPieces() 3762 .add(checkForNoChange(binding, 3763 gen.new Piece(corePath + "terminologies.html#" + binding.getStrength().toCode(), 3764 binding.getStrength().toCode(), binding.getStrength().getDefinition()))); 3765 c.getPieces().add(gen.new Piece(null, ")", null)); 3766 } 3767 } 3768 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 3769 if (!c.getPieces().isEmpty()) 3770 c.addPiece(gen.new Piece("br")); 3771 c.getPieces().add( 3772 checkForNoChange(inv, gen.new Piece(null, inv.getKey() + ": ", null).addStyle("font-weight:bold"))); 3773 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); 3774 } 3775 if (definition.hasFixed()) { 3776 if (!c.getPieces().isEmpty()) 3777 c.addPiece(gen.new Piece("br")); 3778 c.getPieces().add(checkForNoChange(definition.getFixed(), 3779 gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); 3780 String s = buildJson(definition.getFixed()); 3781 String link = null; 3782 if (Utilities.isAbsoluteUrl(s)) 3783 link = pkp.getLinkForUrl(corePath, s); 3784 c.getPieces().add( 3785 checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 3786 } else if (definition.hasPattern()) { 3787 if (!c.getPieces().isEmpty()) 3788 c.addPiece(gen.new Piece("br")); 3789 c.getPieces().add(checkForNoChange(definition.getPattern(), 3790 gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); 3791 c.getPieces().add(checkForNoChange(definition.getPattern(), 3792 gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 3793 } else if (definition.hasExample()) { 3794 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 3795 if (!c.getPieces().isEmpty()) 3796 c.addPiece(gen.new Piece("br")); 3797 c.getPieces().add(checkForNoChange(ex, 3798 gen.new Piece(null, "Example'" + ("".equals("General") ? "" : " " + ex.getLabel() + "'") + ": ", null) 3799 .addStyle("font-weight:bold"))); 3800 c.getPieces().add(checkForNoChange(ex, 3801 gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 3802 } 3803 } 3804 if (definition.hasMaxLength() && definition.getMaxLength() != 0) { 3805 if (!c.getPieces().isEmpty()) 3806 c.addPiece(gen.new Piece("br")); 3807 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), 3808 gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 3809 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), 3810 gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 3811 } 3812 if (profile != null) { 3813 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 3814 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 3815 ElementDefinitionMappingComponent map = null; 3816 for (ElementDefinitionMappingComponent m : definition.getMapping()) 3817 if (m.getIdentity().equals(md.getIdentity())) 3818 map = m; 3819 if (map != null) { 3820 for (int i = 0; i < definition.getMapping().size(); i++) { 3821 c.addPiece(gen.new Piece("br")); 3822 c.getPieces().add( 3823 gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME) 3824 + ": " + map.getMap(), null)); 3825 } 3826 } 3827 } 3828 } 3829 } 3830 if (definition.hasDefinition()) { 3831 if (!c.getPieces().isEmpty()) 3832 c.addPiece(gen.new Piece("br")); 3833 c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold")); 3834 c.addPiece(gen.new Piece("br")); 3835 c.addMarkdown(definition.getDefinition()); 3836// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 3837 } 3838 if (definition.getComment() != null) { 3839 if (!c.getPieces().isEmpty()) 3840 c.addPiece(gen.new Piece("br")); 3841 c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold")); 3842 c.addPiece(gen.new Piece("br")); 3843 c.addMarkdown(definition.getComment()); 3844// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 3845 } 3846 } 3847 } 3848 } 3849 return c; 3850 } 3851 3852 private String buildJson(Type value) throws IOException { 3853 if (value instanceof PrimitiveType) 3854 return ((PrimitiveType) value).asStringValue(); 3855 3856 IParser json = context.newJsonParser(); 3857 return json.composeString(value, null); 3858 } 3859 3860 public String describeSlice(ElementDefinitionSlicingComponent slicing) { 3861 return translate("sd.table", "%s, %s by %s", 3862 slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), 3863 describe(slicing.getRules()), commas(slicing.getDiscriminator())); 3864 } 3865 3866 private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) { 3867 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 3868 for (ElementDefinitionSlicingDiscriminatorComponent id : list) 3869 c.append(id.getType().toCode() + ":" + id.getPath()); 3870 return c.toString(); 3871 } 3872 3873 private String describe(SlicingRules rules) { 3874 if (rules == null) 3875 return translate("sd.table", "Unspecified"); 3876 switch (rules) { 3877 case CLOSED: 3878 return translate("sd.table", "Closed"); 3879 case OPEN: 3880 return translate("sd.table", "Open"); 3881 case OPENATEND: 3882 return translate("sd.table", "Open At End"); 3883 default: 3884 return "??"; 3885 } 3886 } 3887 3888 private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) { 3889 return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) && getChildren(list, e).isEmpty(); 3890 } 3891 3892 private boolean onlyInformationIsMapping(ElementDefinition d) { 3893 return !d.hasShort() && !d.hasDefinition() && !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() 3894 && !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() && !d.hasExample() && !d.hasFixed() 3895 && !d.hasMaxLengthElement() && !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() 3896 && !d.hasMustSupportElement() && !d.hasBinding(); 3897 } 3898 3899 private boolean allAreReference(List<TypeRefComponent> types) { 3900 for (TypeRefComponent t : types) { 3901 if (!t.hasTarget()) 3902 return false; 3903 } 3904 return true; 3905 } 3906 3907 private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) { 3908 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 3909 int i = all.indexOf(element) + 1; 3910 while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) { 3911 if ((all.get(i).getPath().substring(0, element.getPath().length() + 1).equals(element.getPath() + ".")) 3912 && !all.get(i).getPath().substring(element.getPath().length() + 1).contains(".")) 3913 result.add(all.get(i)); 3914 i++; 3915 } 3916 return result; 3917 } 3918 3919 private String tail(String path) { 3920 if (path.contains(".")) 3921 return path.substring(path.lastIndexOf('.') + 1); 3922 else 3923 return path; 3924 } 3925 3926 private boolean isDataType(String value) { 3927 StructureDefinition sd = context.fetchTypeDefinition(value); 3928 if (sd == null) // might be running before all SDs are available 3929 return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", 3930 "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", 3931 "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", "ContactDetail", "Contributor", 3932 "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", 3933 "UsageContext"); 3934 else 3935 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE 3936 && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 3937 } 3938 3939 private boolean isConstrainedDataType(String value) { 3940 StructureDefinition sd = context.fetchTypeDefinition(value); 3941 if (sd == null) // might be running before all SDs are available 3942 return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"); 3943 else 3944 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT; 3945 } 3946 3947 private String baseType(String value) { 3948 StructureDefinition sd = context.fetchTypeDefinition(value); 3949 if (sd != null) // might be running before all SDs are available 3950 return sd.getType(); 3951 if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity")) 3952 return "Quantity"; 3953 throw new Error("Internal error - type not known " + value); 3954 } 3955 3956 public boolean isPrimitive(String value) { 3957 StructureDefinition sd = context.fetchTypeDefinition(value); 3958 if (sd == null) // might be running before all SDs are available 3959 return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", 3960 "decimal", "id", "instant", "integer", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", 3961 "uri", "url", "uuid"); 3962 else 3963 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 3964 } 3965 3966// private static String listStructures(StructureDefinition p) { 3967// StringBuilder b = new StringBuilder(); 3968// boolean first = true; 3969// for (ProfileStructureComponent s : p.getStructure()) { 3970// if (first) 3971// first = false; 3972// else 3973// b.append(", "); 3974// if (pkp != null && pkp.hasLinkFor(s.getType())) 3975// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 3976// else 3977// b.append(s.getType()); 3978// } 3979// return b.toString(); 3980// } 3981 3982 public StructureDefinition getProfile(StructureDefinition source, String url) { 3983 StructureDefinition profile = null; 3984 String code = null; 3985 if (url.startsWith("#")) { 3986 profile = source; 3987 code = url.substring(1); 3988 } else if (context != null) { 3989 String[] parts = url.split("\\#"); 3990 profile = context.fetchResource(StructureDefinition.class, parts[0]); 3991 code = parts.length == 1 ? null : parts[1]; 3992 } 3993 if (profile == null) 3994 return null; 3995 if (code == null) 3996 return profile; 3997 for (Resource r : profile.getContained()) { 3998 if (r instanceof StructureDefinition && r.getId().equals(code)) 3999 return (StructureDefinition) r; 4000 } 4001 return null; 4002 } 4003 4004 public static class ElementDefinitionHolder { 4005 private String name; 4006 private ElementDefinition self; 4007 private int baseIndex = 0; 4008 private List<ElementDefinitionHolder> children; 4009 private boolean placeHolder = false; 4010 4011 public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) { 4012 super(); 4013 this.self = self; 4014 this.name = self.getPath(); 4015 this.placeHolder = isPlaceholder; 4016 children = new ArrayList<ElementDefinitionHolder>(); 4017 } 4018 4019 public ElementDefinitionHolder(ElementDefinition self) { 4020 this(self, false); 4021 } 4022 4023 public ElementDefinition getSelf() { 4024 return self; 4025 } 4026 4027 public List<ElementDefinitionHolder> getChildren() { 4028 return children; 4029 } 4030 4031 public int getBaseIndex() { 4032 return baseIndex; 4033 } 4034 4035 public void setBaseIndex(int baseIndex) { 4036 this.baseIndex = baseIndex; 4037 } 4038 4039 public boolean isPlaceHolder() { 4040 return this.placeHolder; 4041 } 4042 4043 @Override 4044 public String toString() { 4045 if (self.hasSliceName()) 4046 return self.getPath() + "(" + self.getSliceName() + ")"; 4047 else 4048 return self.getPath(); 4049 } 4050 } 4051 4052 public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 4053 4054 private boolean inExtension; 4055 private List<ElementDefinition> snapshot; 4056 private int prefixLength; 4057 private String base; 4058 private String name; 4059 private Set<String> errors = new HashSet<String>(); 4060 4061 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, 4062 int prefixLength, String name) { 4063 this.inExtension = inExtension; 4064 this.snapshot = snapshot; 4065 this.prefixLength = prefixLength; 4066 this.base = base; 4067 this.name = name; 4068 } 4069 4070 @Override 4071 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 4072 if (o1.getBaseIndex() == 0) 4073 o1.setBaseIndex(find(o1.getSelf().getPath())); 4074 if (o2.getBaseIndex() == 0) 4075 o2.setBaseIndex(find(o2.getSelf().getPath())); 4076 return o1.getBaseIndex() - o2.getBaseIndex(); 4077 } 4078 4079 private int find(String path) { 4080 String op = path; 4081 int lc = 0; 4082 String actual = base + path.substring(prefixLength); 4083 for (int i = 0; i < snapshot.size(); i++) { 4084 String p = snapshot.get(i).getPath(); 4085 if (p.equals(actual)) { 4086 return i; 4087 } 4088 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length() - 3)) && !(actual.endsWith("[x]")) 4089 && !actual.substring(p.length() - 3).contains(".")) { 4090 return i; 4091 } 4092 if (path.startsWith(p + ".") && snapshot.get(i).hasContentReference()) { 4093 String ref = snapshot.get(i).getContentReference(); 4094 if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1, 2))) { 4095 actual = base + (ref.substring(1) + "." + path.substring(p.length() + 1)).substring(prefixLength); 4096 path = actual; 4097 } else { 4098 // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter 4099 // instead of #Parameters.parameter, so we have to handle that 4100 actual = base 4101 + (path.substring(0, path.indexOf(".") + 1) + ref.substring(1) + "." + path.substring(p.length() + 1)) 4102 .substring(prefixLength); 4103 path = actual; 4104 } 4105 4106 i = 0; 4107 lc++; 4108 if (lc > MAX_RECURSION_LIMIT) 4109 throw new Error("Internal recursion detection: find() loop path recursion > " + MAX_RECURSION_LIMIT 4110 + " - check paths are valid (for path " + path + "/" + op + ")"); 4111 } 4112 } 4113 if (prefixLength == 0) 4114 errors.add("Differential contains path " + path + " which is not found in the base"); 4115 else 4116 errors.add( 4117 "Differential contains path " + path + " which is actually " + actual + ", which is not found in the base"); 4118 return 0; 4119 } 4120 4121 public void checkForErrors(List<String> errorList) { 4122 if (errors.size() > 0) { 4123// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 4124// for (String s : errors) 4125// b.append("StructureDefinition "+name+": "+s); 4126// throw new DefinitionException(b.toString()); 4127 for (String s : errors) 4128 if (s.startsWith("!")) 4129 errorList.add("!StructureDefinition " + name + ": " + s.substring(1)); 4130 else 4131 errorList.add("StructureDefinition " + name + ": " + s); 4132 } 4133 } 4134 } 4135 4136 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) 4137 throws FHIRException { 4138 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 4139 int lastCount = diffList.size(); 4140 // first, we move the differential elements into a tree 4141 if (diffList.isEmpty()) 4142 return; 4143 4144 ElementDefinitionHolder edh = null; 4145 int i = 0; 4146 if (diffList.get(0).getPath().contains(".")) { 4147 String newPath = diffList.get(0).getPath().split("\\.")[0]; 4148 ElementDefinition e = new ElementDefinition(new StringType(newPath)); 4149 edh = new ElementDefinitionHolder(e, true); 4150 } else { 4151 edh = new ElementDefinitionHolder(diffList.get(0)); 4152 i = 1; 4153 } 4154 4155 boolean hasSlicing = false; 4156 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 4157 for (ElementDefinition elt : diffList) { 4158 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 4159 hasSlicing = true; 4160 break; 4161 } 4162 paths.add(elt.getPath()); 4163 } 4164 if (!hasSlicing) { 4165 // if Differential does not have slicing then safe to pre-sort the list 4166 // so elements and subcomponents are together 4167 Collections.sort(diffList, new ElementNameCompare()); 4168 } 4169 4170 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 4171 4172 // now, we sort the siblings throughout the tree 4173 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name); 4174 sortElements(edh, cmp, errors); 4175 4176 // now, we serialise them back to a list 4177 diffList.clear(); 4178 writeElements(edh, diffList); 4179 4180 if (lastCount != diffList.size()) 4181 errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal"); 4182 } 4183 4184 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 4185 String path = edh.getSelf().getPath(); 4186 final String prefix = path + "."; 4187 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 4188 if (list.get(i).getPath().substring(prefix.length() + 1).contains(".")) { 4189 String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0]; 4190 ElementDefinition e = new ElementDefinition(new StringType(newPath)); 4191 ElementDefinitionHolder child = new ElementDefinitionHolder(e, true); 4192 edh.getChildren().add(child); 4193 i = processElementsIntoTree(child, i, list); 4194 4195 } else { 4196 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 4197 edh.getChildren().add(child); 4198 i = processElementsIntoTree(child, i + 1, list); 4199 } 4200 } 4201 return i; 4202 } 4203 4204 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) 4205 throws FHIRException { 4206 if (edh.getChildren().size() == 1) 4207 // special case - sort needsto allocate base numbers, but there'll be no sort if 4208 // there's only 1 child. So in that case, we just go ahead and allocated base 4209 // number directly 4210 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath()); 4211 else 4212 Collections.sort(edh.getChildren(), cmp); 4213 cmp.checkForErrors(errors); 4214 4215 for (ElementDefinitionHolder child : edh.getChildren()) { 4216 if (child.getChildren().size() > 0) { 4217 ElementDefinitionComparer ccmp = getComparer(cmp, child); 4218 if (ccmp != null) 4219 sortElements(child, ccmp, errors); 4220 } 4221 } 4222 } 4223 4224 public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) 4225 throws FHIRException, Error { 4226 // what we have to check for here is running off the base profile into a data 4227 // type profile 4228 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 4229 ElementDefinitionComparer ccmp; 4230 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) 4231 || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) { 4232 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name); 4233 } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 4234 && child.getSelf().getType().get(0).hasProfile()) { 4235 StructureDefinition profile = context.fetchResource(StructureDefinition.class, 4236 child.getSelf().getType().get(0).getProfile().get(0).getValue()); 4237 if (profile == null) 4238 ccmp = null; // this might happen before everything is loaded. And we don't so much care 4239 // about sot order in this case 4240 else 4241 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), 4242 ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 4243 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) { 4244 StructureDefinition profile = context.fetchResource(StructureDefinition.class, 4245 sdNs(ed.getType().get(0).getWorkingCode())); 4246 if (profile == null) 4247 throw new FHIRException( 4248 "Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 4249 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), 4250 ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 4251 } else if (child.getSelf().getType().size() == 1) { 4252 StructureDefinition profile = context.fetchResource(StructureDefinition.class, 4253 sdNs(child.getSelf().getType().get(0).getWorkingCode())); 4254 if (profile == null) 4255 throw new FHIRException( 4256 "Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 4257 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), 4258 child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 4259 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 4260 String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2"); 4261 String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2"); 4262 String p = childLastNode.substring(edLastNode.length() - 3); 4263 if (isPrimitive(Utilities.uncapitalize(p))) 4264 p = Utilities.uncapitalize(p); 4265 StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p)); 4266 if (sd == null) 4267 throw new Error("Unable to find profile '" + p + "' at " + ed.getId()); 4268 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), 4269 cmp.name); 4270 } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) { 4271 for (TypeRefComponent t : child.getSelf().getType()) { 4272 if (!t.getWorkingCode().equals("Reference")) { 4273 throw new Error( 4274 "Can't have children on an element with a polymorphic type - you must slice and constrain the types first (sortElements: " 4275 + ed.getPath() + ":" + typeCode(ed.getType()) + ")"); 4276 } 4277 } 4278 StructureDefinition profile = context.fetchResource(StructureDefinition.class, 4279 sdNs(ed.getType().get(0).getWorkingCode())); 4280 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), 4281 ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 4282 } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) { 4283 for (TypeRefComponent t : ed.getType()) { 4284 if (!t.getWorkingCode().equals("Reference")) { 4285 throw new Error("Not handled yet (sortElements: " + ed.getPath() + ":" + typeCode(ed.getType()) + ")"); 4286 } 4287 } 4288 StructureDefinition profile = context.fetchResource(StructureDefinition.class, 4289 sdNs(ed.getType().get(0).getWorkingCode())); 4290 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), 4291 ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 4292 } else { 4293 // this is allowed if we only profile the extensions 4294 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element")); 4295 if (profile == null) 4296 throw new FHIRException( 4297 "Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 4298 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", 4299 child.getSelf().getPath().length(), cmp.name); 4300// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 4301 } 4302 return ccmp; 4303 } 4304 4305 private static String sdNs(String type) { 4306 return sdNs(type, null); 4307 } 4308 4309 public static String sdNs(String type, String overrideVersionNs) { 4310 if (Utilities.isAbsoluteUrl(type)) 4311 return type; 4312 else if (overrideVersionNs != null) 4313 return Utilities.pathURL(overrideVersionNs, type); 4314 else 4315 return "http://hl7.org/fhir/StructureDefinition/" + type; 4316 } 4317 4318 private boolean isAbstract(String code) { 4319 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") 4320 || code.equals("DomainResource"); 4321 } 4322 4323 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 4324 if (!edh.isPlaceHolder()) 4325 list.add(edh.getSelf()); 4326 for (ElementDefinitionHolder child : edh.getChildren()) { 4327 writeElements(child, list); 4328 } 4329 } 4330 4331 /** 4332 * First compare element by path then by name if same 4333 */ 4334 private static class ElementNameCompare implements Comparator<ElementDefinition> { 4335 4336 @Override 4337 public int compare(ElementDefinition o1, ElementDefinition o2) { 4338 String path1 = normalizePath(o1); 4339 String path2 = normalizePath(o2); 4340 int cmp = path1.compareTo(path2); 4341 if (cmp == 0) { 4342 String name1 = o1.hasSliceName() ? o1.getSliceName() : ""; 4343 String name2 = o2.hasSliceName() ? o2.getSliceName() : ""; 4344 cmp = name1.compareTo(name2); 4345 } 4346 return cmp; 4347 } 4348 4349 private static String normalizePath(ElementDefinition e) { 4350 if (!e.hasPath()) 4351 return ""; 4352 String path = e.getPath(); 4353 // if sorting element names make sure onset[x] appears before onsetAge, 4354 // onsetDate, etc. 4355 // so strip off the [x] suffix when comparing the path names. 4356 if (path.endsWith("[x]")) { 4357 path = path.substring(0, path.length() - 3); 4358 } 4359 return path; 4360 } 4361 4362 } 4363 4364 // generate schematrons for the rules in a structure definition 4365 public void generateSchematrons(OutputStream dest, StructureDefinition structure) 4366 throws IOException, DefinitionException { 4367 if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT) 4368 throw new DefinitionException("not the right kind of structure to generate schematrons for"); 4369 if (!structure.hasSnapshot()) 4370 throw new DefinitionException("needs a snapshot"); 4371 4372 StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition()); 4373 4374 if (base != null) { 4375 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 4376 4377 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 4378 generateForChildren(sch, "f:" + ed.getPath(), ed, structure, base); 4379 sch.dump(); 4380 } 4381 } 4382 4383 // generate a CSV representation of the structure definition 4384 public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) 4385 throws IOException, DefinitionException, Exception { 4386 if (!structure.hasSnapshot()) 4387 throw new DefinitionException("needs a snapshot"); 4388 4389 CSVWriter csv = new CSVWriter(dest, structure, asXml); 4390 4391 for (ElementDefinition child : structure.getSnapshot().getElement()) { 4392 csv.processElement(child); 4393 } 4394 csv.dump(); 4395 } 4396 4397 // generate an Excel representation of the structure definition 4398 public void generateXlsx(OutputStream dest, StructureDefinition structure, boolean asXml, 4399 boolean hideMustSupportFalse) throws IOException, DefinitionException, Exception { 4400 if (!structure.hasSnapshot()) 4401 throw new DefinitionException("needs a snapshot"); 4402 4403 XLSXWriter xlsx = new XLSXWriter(dest, structure, asXml, hideMustSupportFalse); 4404 4405 for (ElementDefinition child : structure.getSnapshot().getElement()) { 4406 xlsx.processElement(child); 4407 } 4408 xlsx.dump(); 4409 xlsx.close(); 4410 } 4411 4412 private class Slicer extends ElementDefinitionSlicingComponent { 4413 String criteria = ""; 4414 String name = ""; 4415 boolean check; 4416 4417 public Slicer(boolean cantCheck) { 4418 super(); 4419 this.check = cantCheck; 4420 } 4421 } 4422 4423 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, 4424 StructureDefinition structure) { 4425 // given a child in a structure, it's sliced. figure out the slicing xpath 4426 if (child.getPath().endsWith(".extension")) { 4427 ElementDefinition ued = getUrlFor(structure, child); 4428 if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile()))) 4429 return new Slicer(false); 4430 else { 4431 Slicer s = new Slicer(true); 4432 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() 4433 : ((UriType) ued.getFixed()).asStringValue(); 4434 s.name = " with URL = '" + url + "'"; 4435 s.criteria = "[@url = '" + url + "']"; 4436 return s; 4437 } 4438 } else 4439 return new Slicer(false); 4440 } 4441 4442 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, 4443 StructureDefinition structure, StructureDefinition base) throws IOException { 4444 // generateForChild(txt, structure, child); 4445 List<ElementDefinition> children = getChildList(structure, ed); 4446 String sliceName = null; 4447 ElementDefinitionSlicingComponent slicing = null; 4448 for (ElementDefinition child : children) { 4449 String name = tail(child.getPath()); 4450 if (child.hasSlicing()) { 4451 sliceName = name; 4452 slicing = child.getSlicing(); 4453 } else if (!name.equals(sliceName)) 4454 slicing = null; 4455 4456 ElementDefinition based = getByPath(base, child.getPath()); 4457 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 4458 boolean doMax = child.hasMax() && !child.getMax().equals("*") 4459 && (based == null || (!child.getMax().equals(based.getMax()))); 4460 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 4461 if (slicer.check) { 4462 if (doMin || doMax) { 4463 Section s = sch.section(xpath); 4464 Rule r = s.rule(xpath); 4465 if (doMin) 4466 r.assrt("count(f:" + name + slicer.criteria + ") >= " + Integer.toString(child.getMin()), 4467 name + slicer.name + ": minimum cardinality of '" + name + "' is " + Integer.toString(child.getMin())); 4468 if (doMax) 4469 r.assrt("count(f:" + name + slicer.criteria + ") <= " + child.getMax(), 4470 name + slicer.name + ": maximum cardinality of '" + name + "' is " + child.getMax()); 4471 } 4472 } 4473 } 4474 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 4475 if (inv.hasXpath()) { 4476 Section s = sch.section(ed.getPath()); 4477 Rule r = s.rule(xpath); 4478 r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId() + ": " : "") + inv.getHuman() 4479 + (inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 4480 } 4481 } 4482 for (ElementDefinition child : children) { 4483 String name = tail(child.getPath()); 4484 generateForChildren(sch, xpath + "/f:" + name, child, structure, base); 4485 } 4486 } 4487 4488 private ElementDefinition getByPath(StructureDefinition base, String path) { 4489 for (ElementDefinition ed : base.getSnapshot().getElement()) { 4490 if (ed.getPath().equals(path)) 4491 return ed; 4492 if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length() - 3 4493 && ed.getPath().substring(0, ed.getPath().length() - 3).equals(path.substring(0, ed.getPath().length() - 3))) 4494 return ed; 4495 } 4496 return null; 4497 } 4498 4499 public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException { 4500 if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { 4501 if (!sd.hasDifferential()) 4502 sd.setDifferential(new StructureDefinitionDifferentialComponent()); 4503 generateIds(sd.getDifferential().getElement(), sd.getUrl()); 4504 } 4505 if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { 4506 if (!sd.hasSnapshot()) 4507 sd.setSnapshot(new StructureDefinitionSnapshotComponent()); 4508 generateIds(sd.getSnapshot().getElement(), sd.getUrl()); 4509 } 4510 } 4511 4512 private boolean hasMissingIds(List<ElementDefinition> list) { 4513 for (ElementDefinition ed : list) { 4514 if (!ed.hasId()) 4515 return true; 4516 } 4517 return false; 4518 } 4519 4520 public class SliceList { 4521 4522 private Map<String, String> slices = new HashMap<>(); 4523 4524 public void seeElement(ElementDefinition ed) { 4525 Iterator<Map.Entry<String, String>> iter = slices.entrySet().iterator(); 4526 while (iter.hasNext()) { 4527 Map.Entry<String, String> entry = iter.next(); 4528 if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath())) 4529 iter.remove(); 4530 } 4531 4532 if (ed.hasSliceName()) 4533 slices.put(ed.getPath(), ed.getSliceName()); 4534 } 4535 4536 public String[] analyse(List<String> paths) { 4537 String s = paths.get(0); 4538 String[] res = new String[paths.size()]; 4539 res[0] = null; 4540 for (int i = 1; i < paths.size(); i++) { 4541 s = s + "." + paths.get(i); 4542 if (slices.containsKey(s)) 4543 res[i] = slices.get(s); 4544 else 4545 res[i] = null; 4546 } 4547 return res; 4548 } 4549 4550 } 4551 4552 private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException { 4553 if (list.isEmpty()) 4554 return; 4555 4556 Map<String, String> idMap = new HashMap<String, String>(); 4557 Map<String, String> idList = new HashMap<String, String>(); 4558 4559 SliceList sliceInfo = new SliceList(); 4560 // first pass, update the element ids 4561 for (ElementDefinition ed : list) { 4562 List<String> paths = new ArrayList<String>(); 4563 if (!ed.hasPath()) 4564 throw new DefinitionException( 4565 "No path on element Definition " + Integer.toString(list.indexOf(ed)) + " in " + name); 4566 sliceInfo.seeElement(ed); 4567 String[] pl = ed.getPath().split("\\."); 4568 for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus 4569 paths.add(pl[i]); 4570 String slices[] = sliceInfo.analyse(paths); 4571 4572 StringBuilder b = new StringBuilder(); 4573 b.append(paths.get(0)); 4574 for (int i = 1; i < paths.size(); i++) { 4575 b.append("."); 4576 String s = paths.get(i); 4577 String p = slices[i]; 4578 b.append(s); 4579 if (p != null) { 4580 b.append(":"); 4581 b.append(p); 4582 } 4583 } 4584 String bs = b.toString(); 4585 idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs); 4586 ed.setId(bs); 4587 if (idList.containsKey(bs)) { 4588 if (exception || messages == null) 4589 throw new DefinitionException( 4590 "Same id '" + bs + "'on multiple elements " + idList.get(bs) + "/" + ed.getPath() + " in " + name); 4591 else 4592 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, 4593 name + "." + bs, "Duplicate Element id " + bs, ValidationMessage.IssueSeverity.ERROR)); 4594 } 4595 idList.put(bs, ed.getPath()); 4596 if (ed.hasContentReference()) { 4597 String s = ed.getContentReference().substring(1); 4598 if (idMap.containsKey(s)) 4599 ed.setContentReference("#" + idMap.get(s)); 4600 4601 } 4602 } 4603 // second path - fix up any broken path based id references 4604 4605 } 4606 4607// private String describeExtension(ElementDefinition ed) { 4608// if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) 4609// return ""; 4610// return "$"+urlTail(ed.getTypeFirstRep().getProfile()); 4611// } 4612// 4613 4614 private String urlTail(String profile) { 4615 return profile.contains("/") ? profile.substring(profile.lastIndexOf("/") + 1) : profile; 4616 } 4617 4618 private String checkName(String name) { 4619// if (name.contains(".")) 4620//// throw new Exception("Illegal name "+name+": no '.'"); 4621// if (name.contains(" ")) 4622// throw new Exception("Illegal name "+name+": no spaces"); 4623 StringBuilder b = new StringBuilder(); 4624 for (char c : name.toCharArray()) { 4625 if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']')) 4626 b.append(c); 4627 } 4628 return b.toString().toLowerCase(); 4629 } 4630 4631 private int charCount(String path, char t) { 4632 int res = 0; 4633 for (char ch : path.toCharArray()) { 4634 if (ch == t) 4635 res++; 4636 } 4637 return res; 4638 } 4639 4640// 4641//private void generateForChild(TextStreamWriter txt, 4642// StructureDefinition structure, ElementDefinition child) { 4643// // TODO Auto-generated method stub 4644// 4645//} 4646 4647 private interface ExampleValueAccessor { 4648 Type getExampleValue(ElementDefinition ed); 4649 4650 String getId(); 4651 } 4652 4653 private class BaseExampleValueAccessor implements ExampleValueAccessor { 4654 @Override 4655 public Type getExampleValue(ElementDefinition ed) { 4656 if (ed.hasFixed()) 4657 return ed.getFixed(); 4658 if (ed.hasExample()) 4659 return ed.getExample().get(0).getValue(); 4660 else 4661 return null; 4662 } 4663 4664 @Override 4665 public String getId() { 4666 return "-genexample"; 4667 } 4668 } 4669 4670 private class ExtendedExampleValueAccessor implements ExampleValueAccessor { 4671 private String index; 4672 4673 public ExtendedExampleValueAccessor(String index) { 4674 this.index = index; 4675 } 4676 4677 @Override 4678 public Type getExampleValue(ElementDefinition ed) { 4679 if (ed.hasFixed()) 4680 return ed.getFixed(); 4681 for (Extension ex : ed.getExtension()) { 4682 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 4683 Type value = ToolingExtensions.getExtension(ex, "exValue").getValue(); 4684 if (index.equals(ndx) && value != null) 4685 return value; 4686 } 4687 return null; 4688 } 4689 4690 @Override 4691 public String getId() { 4692 return "-genexample-" + index; 4693 } 4694 } 4695 4696 public List<org.hl7.fhir.r4.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) 4697 throws FHIRException { 4698 List<org.hl7.fhir.r4.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>(); 4699 if (sd.hasSnapshot()) { 4700 if (evenWhenNoExamples || hasAnyExampleValues(sd)) 4701 examples.add(generateExample(sd, new BaseExampleValueAccessor())); 4702 for (int i = 1; i <= 50; i++) { 4703 if (hasAnyExampleValues(sd, Integer.toString(i))) 4704 examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i)))); 4705 } 4706 } 4707 return examples; 4708 } 4709 4710 private org.hl7.fhir.r4.elementmodel.Element generateExample(StructureDefinition profile, 4711 ExampleValueAccessor accessor) throws FHIRException { 4712 ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 4713 org.hl7.fhir.r4.elementmodel.Element r = new org.hl7.fhir.r4.elementmodel.Element(ed.getPath(), 4714 new Property(context, ed, profile)); 4715 List<ElementDefinition> children = getChildMap(profile, ed); 4716 for (ElementDefinition child : children) { 4717 if (child.getPath().endsWith(".id")) { 4718 org.hl7.fhir.r4.elementmodel.Element id = new org.hl7.fhir.r4.elementmodel.Element("id", 4719 new Property(context, child, profile)); 4720 id.setValue(profile.getId() + accessor.getId()); 4721 r.getChildren().add(id); 4722 } else { 4723 org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor); 4724 if (e != null) 4725 r.getChildren().add(e); 4726 } 4727 } 4728 return r; 4729 } 4730 4731 private org.hl7.fhir.r4.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, 4732 ExampleValueAccessor accessor) throws FHIRException { 4733 Type v = accessor.getExampleValue(ed); 4734 if (v != null) { 4735 return new ObjectConverter(context).convert(new Property(context, ed, profile), v); 4736 } else { 4737 org.hl7.fhir.r4.elementmodel.Element res = new org.hl7.fhir.r4.elementmodel.Element(tail(ed.getPath()), 4738 new Property(context, ed, profile)); 4739 boolean hasValue = false; 4740 List<ElementDefinition> children = getChildMap(profile, ed); 4741 for (ElementDefinition child : children) { 4742 if (!child.hasContentReference()) { 4743 org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor); 4744 if (e != null) { 4745 hasValue = true; 4746 res.getChildren().add(e); 4747 } 4748 } 4749 } 4750 if (hasValue) 4751 return res; 4752 else 4753 return null; 4754 } 4755 } 4756 4757 private boolean hasAnyExampleValues(StructureDefinition sd, String index) { 4758 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4759 for (Extension ex : ed.getExtension()) { 4760 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 4761 Extension exv = ToolingExtensions.getExtension(ex, "exValue"); 4762 if (exv != null) { 4763 Type value = exv.getValue(); 4764 if (index.equals(ndx) && value != null) 4765 return true; 4766 } 4767 } 4768 return false; 4769 } 4770 4771 private boolean hasAnyExampleValues(StructureDefinition sd) { 4772 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4773 if (ed.hasExample()) 4774 return true; 4775 return false; 4776 } 4777 4778 public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException { 4779 sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy()); 4780 4781 if (sd.hasBaseDefinition()) { 4782 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 4783 if (base == null) 4784 throw new FHIRException( 4785 "Unable to find base definition for logical model: " + sd.getBaseDefinition() + " from " + sd.getUrl()); 4786 copyElements(sd, base.getSnapshot().getElement()); 4787 } 4788 copyElements(sd, sd.getDifferential().getElement()); 4789 } 4790 4791 private void copyElements(StructureDefinition sd, List<ElementDefinition> list) { 4792 for (ElementDefinition ed : list) { 4793 if (ed.getPath().contains(".")) { 4794 ElementDefinition n = ed.copy(); 4795 n.setPath(sd.getSnapshot().getElementFirstRep().getPath() + "." 4796 + ed.getPath().substring(ed.getPath().indexOf(".") + 1)); 4797 sd.getSnapshot().addElement(n); 4798 } 4799 } 4800 } 4801 4802 public void cleanUpDifferential(StructureDefinition sd) { 4803 if (sd.getDifferential().getElement().size() > 1) 4804 cleanUpDifferential(sd, 1); 4805 } 4806 4807 private void cleanUpDifferential(StructureDefinition sd, int start) { 4808 int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.'); 4809 int c = start; 4810 int len = sd.getDifferential().getElement().size(); 4811 HashSet<String> paths = new HashSet<String>(); 4812 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) { 4813 ElementDefinition ed = sd.getDifferential().getElement().get(c); 4814 if (!paths.contains(ed.getPath())) { 4815 paths.add(ed.getPath()); 4816 int ic = c + 1; 4817 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4818 ic++; 4819 ElementDefinition slicer = null; 4820 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 4821 slices.add(ed); 4822 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) { 4823 ElementDefinition edi = sd.getDifferential().getElement().get(ic); 4824 if (ed.getPath().equals(edi.getPath())) { 4825 if (slicer == null) { 4826 slicer = new ElementDefinition(); 4827 slicer.setPath(edi.getPath()); 4828 slicer.getSlicing().setRules(SlicingRules.OPEN); 4829 sd.getDifferential().getElement().add(c, slicer); 4830 c++; 4831 ic++; 4832 } 4833 slices.add(edi); 4834 } 4835 ic++; 4836 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4837 ic++; 4838 } 4839 // now we're at the end, we're going to figure out the slicing discriminator 4840 if (slicer != null) 4841 determineSlicing(slicer, slices); 4842 } 4843 c++; 4844 if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) { 4845 cleanUpDifferential(sd, c); 4846 c++; 4847 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 4848 c++; 4849 } 4850 } 4851 } 4852 4853 private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) { 4854 // first, name them 4855 int i = 0; 4856 for (ElementDefinition ed : slices) { 4857 if (ed.hasUserData("slice-name")) { 4858 ed.setSliceName(ed.getUserString("slice-name")); 4859 } else { 4860 i++; 4861 ed.setSliceName("slice-" + Integer.toString(i)); 4862 } 4863 } 4864 // now, the hard bit, how are they differentiated? 4865 // right now, we hard code this... 4866 if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension")) 4867 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 4868 else if (slicer.getPath().equals("DiagnosticReport.result")) 4869 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code"); 4870 else if (slicer.getPath().equals("Observation.related")) 4871 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code"); 4872 else if (slicer.getPath().equals("Bundle.entry")) 4873 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile"); 4874 else 4875 throw new Error("No slicing for " + slicer.getPath()); 4876 } 4877 4878 public class SpanEntry { 4879 private List<SpanEntry> children = new ArrayList<SpanEntry>(); 4880 private boolean profile; 4881 private String id; 4882 private String name; 4883 private String resType; 4884 private String cardinality; 4885 private String description; 4886 private String profileLink; 4887 private String resLink; 4888 private String type; 4889 4890 public String getName() { 4891 return name; 4892 } 4893 4894 public void setName(String name) { 4895 this.name = name; 4896 } 4897 4898 public String getResType() { 4899 return resType; 4900 } 4901 4902 public void setResType(String resType) { 4903 this.resType = resType; 4904 } 4905 4906 public String getCardinality() { 4907 return cardinality; 4908 } 4909 4910 public void setCardinality(String cardinality) { 4911 this.cardinality = cardinality; 4912 } 4913 4914 public String getDescription() { 4915 return description; 4916 } 4917 4918 public void setDescription(String description) { 4919 this.description = description; 4920 } 4921 4922 public String getProfileLink() { 4923 return profileLink; 4924 } 4925 4926 public void setProfileLink(String profileLink) { 4927 this.profileLink = profileLink; 4928 } 4929 4930 public String getResLink() { 4931 return resLink; 4932 } 4933 4934 public void setResLink(String resLink) { 4935 this.resLink = resLink; 4936 } 4937 4938 public String getId() { 4939 return id; 4940 } 4941 4942 public void setId(String id) { 4943 this.id = id; 4944 } 4945 4946 public boolean isProfile() { 4947 return profile; 4948 } 4949 4950 public void setProfile(boolean profile) { 4951 this.profile = profile; 4952 } 4953 4954 public List<SpanEntry> getChildren() { 4955 return children; 4956 } 4957 4958 public String getType() { 4959 return type; 4960 } 4961 4962 public void setType(String type) { 4963 this.type = type; 4964 } 4965 4966 } 4967 4968 public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, 4969 String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException { 4970 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, false, true); 4971 TableModel model = initSpanningTable(gen, "", false, profile.getId()); 4972 Set<String> processed = new HashSet<String>(); 4973 SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix); 4974 4975 genSpanEntry(gen, model.getRows(), span); 4976 return gen.generate(model, "", 0, outputTracker); 4977 } 4978 4979 private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, 4980 Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException { 4981 SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile); 4982 boolean wantProcess = !processed.contains(profile.getUrl()); 4983 processed.add(profile.getUrl()); 4984 if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 4985 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 4986 if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) { 4987 String card = getCardinality(ed, profile.getSnapshot().getElement()); 4988 if (!card.endsWith(".0")) { 4989 List<String> refProfiles = listReferenceProfiles(ed); 4990 if (refProfiles.size() > 0) { 4991 String uri = refProfiles.get(0); 4992 if (uri != null) { 4993 StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri); 4994 if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT 4995 && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) { 4996 res.getChildren().add( 4997 buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix)); 4998 } 4999 } 5000 } 5001 } 5002 } 5003 } 5004 } 5005 return res; 5006 } 5007 5008 private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) { 5009 int min = ed.getMin(); 5010 int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax()); 5011 while (ed != null && ed.getPath().contains(".")) { 5012 ed = findParent(ed, list); 5013 if (ed.getMax().equals("0")) 5014 max = 0; 5015 else if (!ed.getMax().equals("1") && !ed.hasSlicing()) 5016 max = Integer.MAX_VALUE; 5017 if (ed.getMin() == 0) 5018 min = 0; 5019 } 5020 return Integer.toString(min) + ".." + (max == Integer.MAX_VALUE ? "*" : Integer.toString(max)); 5021 } 5022 5023 private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) { 5024 int i = list.indexOf(ed) - 1; 5025 while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath() + ".")) 5026 i--; 5027 if (i == -1) 5028 return null; 5029 else 5030 return list.get(i); 5031 } 5032 5033 private List<String> listReferenceProfiles(ElementDefinition ed) { 5034 List<String> res = new ArrayList<String>(); 5035 for (TypeRefComponent tr : ed.getType()) { 5036 // code is null if we're dealing with "value" and profile is null if we just 5037 // have Reference() 5038 if (tr.hasTarget() && tr.hasTargetProfile()) 5039 for (UriType u : tr.getTargetProfile()) 5040 res.add(u.getValue()); 5041 } 5042 return res; 5043 } 5044 5045 private String nameForElement(ElementDefinition ed) { 5046 return ed.getPath().substring(ed.getPath().indexOf(".") + 1); 5047 } 5048 5049 private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) 5050 throws IOException { 5051 SpanEntry res = new SpanEntry(); 5052 res.setName(name); 5053 res.setCardinality(cardinality); 5054 res.setProfileLink(profile.getUserString("path")); 5055 res.setResType(profile.getType()); 5056 StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType()); 5057 if (base != null) 5058 res.setResLink(base.getUserString("path")); 5059 res.setId(profile.getId()); 5060 res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT); 5061 StringBuilder b = new StringBuilder(); 5062 b.append(res.getResType()); 5063 boolean first = true; 5064 boolean open = false; 5065 if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 5066 res.setDescription(profile.getName()); 5067 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 5068 if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) { 5069 if (first) { 5070 open = true; 5071 first = false; 5072 b.append("["); 5073 } else { 5074 b.append(", "); 5075 } 5076 b.append(tail(ed.getBase().getPath())); 5077 b.append("="); 5078 b.append(summarize(ed.getFixed())); 5079 } 5080 } 5081 if (open) 5082 b.append("]"); 5083 } else 5084 res.setDescription("Base FHIR " + profile.getName()); 5085 res.setType(b.toString()); 5086 return res; 5087 } 5088 5089 private String summarize(Type value) throws IOException { 5090 if (value instanceof Coding) 5091 return summarizeCoding((Coding) value); 5092 else if (value instanceof CodeableConcept) 5093 return summarizeCodeableConcept((CodeableConcept) value); 5094 else 5095 return buildJson(value); 5096 } 5097 5098 private String summarizeCoding(Coding value) { 5099 String uri = value.getSystem(); 5100 String system = NarrativeGenerator.describeSystem(uri); 5101 if (Utilities.isURL(system)) { 5102 if (system.equals("http://cap.org/protocols")) 5103 system = "CAP Code"; 5104 } 5105 return system + " " + value.getCode(); 5106 } 5107 5108 private String summarizeCodeableConcept(CodeableConcept value) { 5109 if (value.hasCoding()) 5110 return summarizeCoding(value.getCodingFirstRep()); 5111 else 5112 return value.getText(); 5113 } 5114 5115 private boolean isKeyProperty(String path) { 5116 return Utilities.existsInList(path, "Observation.code"); 5117 } 5118 5119 public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) { 5120 TableModel model = gen.new TableModel(id, false); 5121 5122 model.setDocoImg(prefix + "help16.png"); 5123 model.setDocoRef(prefix + "formats.html#table"); // todo: change to graph definition 5124 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0)); 5125 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Card.", 5126 "Minimum and Maximum # of times the the element can appear in the instance", null, 0)); 5127 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0)); 5128 model.getTitles() 5129 .add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0)); 5130 return model; 5131 } 5132 5133 private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException { 5134 Row row = gen.new Row(); 5135 rows.add(row); 5136 row.setAnchor(span.getId()); 5137 // row.setColor(..?); 5138 if (span.isProfile()) 5139 row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE); 5140 else 5141 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 5142 5143 row.getCells().add(gen.new Cell(null, null, span.getName(), null, null)); 5144 row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null)); 5145 row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null)); 5146 row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null)); 5147 5148 for (SpanEntry child : span.getChildren()) 5149 genSpanEntry(gen, row.getSubRows(), child); 5150 } 5151 5152 public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, 5153 boolean isExists) { 5154 if (discriminator.endsWith("@pattern")) 5155 return makeDiscriminator(DiscriminatorType.PATTERN, 5156 discriminator.length() == 8 ? "" : discriminator.substring(0, discriminator.length() - 9)); 5157 if (discriminator.endsWith("@profile")) 5158 return makeDiscriminator(DiscriminatorType.PROFILE, 5159 discriminator.length() == 8 ? "" : discriminator.substring(0, discriminator.length() - 9)); 5160 if (discriminator.endsWith("@type")) 5161 return makeDiscriminator(DiscriminatorType.TYPE, 5162 discriminator.length() == 5 ? "" : discriminator.substring(0, discriminator.length() - 6)); 5163 if (discriminator.endsWith("@exists")) 5164 return makeDiscriminator(DiscriminatorType.EXISTS, 5165 discriminator.length() == 7 ? "" : discriminator.substring(0, discriminator.length() - 8)); 5166 if (isExists) 5167 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 5168 return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator); 5169 } 5170 5171 private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) { 5172 return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType) 5173 .setPath(Utilities.noString(str) ? "$this" : str); 5174 } 5175 5176 public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException { 5177 switch (t.getType()) { 5178 case PROFILE: 5179 return t.getPath() + "/@profile"; 5180 case TYPE: 5181 return t.getPath() + "/@type"; 5182 case VALUE: 5183 return t.getPath(); 5184 case PATTERN: 5185 return t.getPath(); 5186 case EXISTS: 5187 return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - 5188 // one with minOccurs=1 and other with maxOccur=0 5189 default: 5190 throw new FHIRException("Unable to represent " + t.getType().toCode() + ":" + t.getPath() + " in R2"); 5191 } 5192 } 5193 5194 public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) { 5195 String epath = url.substring(54); 5196 if (!epath.contains(".")) 5197 return null; 5198 String type = epath.substring(0, epath.indexOf(".")); 5199 StructureDefinition sd = context.fetchTypeDefinition(type); 5200 if (sd == null) 5201 return null; 5202 ElementDefinition ed = null; 5203 for (ElementDefinition t : sd.getSnapshot().getElement()) { 5204 if (t.getPath().equals(epath)) { 5205 ed = t; 5206 break; 5207 } 5208 } 5209 if (ed == null) 5210 return null; 5211 if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) { 5212 return null; 5213 } else { 5214 StructureDefinition template = context.fetchResource(StructureDefinition.class, 5215 "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"); 5216 StructureDefinition ext = template.copy(); 5217 ext.setUrl(url); 5218 ext.setId("extension-" + epath); 5219 ext.setName("Extension-" + epath); 5220 ext.setTitle("Extension for r4 " + epath); 5221 ext.setStatus(sd.getStatus()); 5222 ext.setDate(sd.getDate()); 5223 ext.getContact().clear(); 5224 ext.getContact().addAll(sd.getContact()); 5225 ext.setFhirVersion(sd.getFhirVersion()); 5226 ext.setDescription(ed.getDefinition()); 5227 ext.getContext().clear(); 5228 ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf("."))); 5229 ext.getDifferential().getElement().clear(); 5230 ext.getSnapshot().getElement().get(3).setFixed(new UriType(url)); 5231 ext.getSnapshot().getElement().set(4, ed.copy()); 5232 ext.getSnapshot().getElement().get(4).setPath("Extension.value" + Utilities.capitalize(ed.typeSummary())); 5233 return ext; 5234 } 5235 5236 } 5237 5238 public boolean isThrowException() { 5239 return exception; 5240 } 5241 5242 public void setThrowException(boolean exception) { 5243 this.exception = exception; 5244 } 5245 5246 public TerminologyServiceOptions getTerminologyServiceOptions() { 5247 return terminologyServiceOptions; 5248 } 5249 5250 public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) { 5251 this.terminologyServiceOptions = terminologyServiceOptions; 5252 } 5253 5254 public boolean isNewSlicingProcessing() { 5255 return newSlicingProcessing; 5256 } 5257 5258 public void setNewSlicingProcessing(boolean newSlicingProcessing) { 5259 this.newSlicingProcessing = newSlicingProcessing; 5260 } 5261 5262 public boolean isDebug() { 5263 return debug; 5264 } 5265 5266 public void setDebug(boolean debug) { 5267 this.debug = debug; 5268 } 5269 5270 5271 public static boolean isExtensionDefinition(StructureDefinition sd) { 5272 return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension"); 5273 } 5274 5275 public static boolean isSimpleExtension(StructureDefinition sd) { 5276 if (!isExtensionDefinition(sd)) { 5277 return false; 5278 } 5279 ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); 5280 return value != null && !value.isProhibited(); 5281 } 5282 5283 public static boolean isComplexExtension(StructureDefinition sd) { 5284 if (!isExtensionDefinition(sd)) { 5285 return false; 5286 } 5287 ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); 5288 return value == null || value.isProhibited(); 5289 } 5290 5291 public static boolean isModifierExtension(StructureDefinition sd) { 5292 ElementDefinition defn = sd.getSnapshot().getElementByPath("Extension"); 5293 return defn.getIsModifier(); 5294 } 5295 5296}