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