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