001package org.hl7.fhir.r4.conformance; 002 003import java.io.IOException; 004import java.io.OutputStream; 005import java.util.ArrayList; 006import java.util.Collections; 007import java.util.Comparator; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.Iterator; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014 015import org.apache.commons.lang3.StringUtils; 016import org.hl7.fhir.exceptions.DefinitionException; 017import org.hl7.fhir.exceptions.FHIRException; 018import org.hl7.fhir.exceptions.FHIRFormatError; 019import org.hl7.fhir.r4.conformance.ProfileUtilities.SliceList; 020import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution; 021import org.hl7.fhir.r4.context.IWorkerContext; 022import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult; 023import org.hl7.fhir.r4.elementmodel.ObjectConverter; 024import org.hl7.fhir.r4.elementmodel.Property; 025import org.hl7.fhir.r4.formats.IParser; 026import org.hl7.fhir.r4.model.Base; 027import org.hl7.fhir.r4.model.BooleanType; 028import org.hl7.fhir.r4.model.CanonicalType; 029import org.hl7.fhir.r4.model.CodeType; 030import org.hl7.fhir.r4.model.CodeableConcept; 031import org.hl7.fhir.r4.model.Coding; 032import org.hl7.fhir.r4.model.Element; 033import org.hl7.fhir.r4.model.ElementDefinition; 034import org.hl7.fhir.r4.model.ElementDefinition.AggregationMode; 035import org.hl7.fhir.r4.model.ElementDefinition.DiscriminatorType; 036import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBaseComponent; 037import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 038import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent; 039import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionExampleComponent; 040import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent; 041import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent; 042import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 043import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules; 044import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 045import org.hl7.fhir.r4.model.Enumeration; 046import org.hl7.fhir.r4.model.Enumerations.BindingStrength; 047import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 048import org.hl7.fhir.r4.model.Extension; 049import org.hl7.fhir.r4.model.IntegerType; 050import org.hl7.fhir.r4.model.PrimitiveType; 051import org.hl7.fhir.r4.model.Quantity; 052import org.hl7.fhir.r4.model.Reference; 053import org.hl7.fhir.r4.model.Resource; 054import org.hl7.fhir.r4.model.StringType; 055import org.hl7.fhir.r4.model.StructureDefinition; 056import org.hl7.fhir.r4.model.StructureDefinition.ExtensionContextType; 057import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionContextComponent; 058import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionDifferentialComponent; 059import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 060import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent; 061import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent; 062import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 063import org.hl7.fhir.r4.model.Type; 064import org.hl7.fhir.r4.model.UriType; 065import org.hl7.fhir.r4.model.ValueSet; 066import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; 067import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 068import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 069import org.hl7.fhir.r4.utils.NarrativeGenerator; 070import org.hl7.fhir.r4.utils.ToolingExtensions; 071import org.hl7.fhir.r4.utils.TranslatingUtilities; 072import org.hl7.fhir.r4.utils.formats.CSVWriter; 073import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 074import org.hl7.fhir.utilities.Utilities; 075import org.hl7.fhir.utilities.validation.ValidationMessage; 076import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 077import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 078import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 079import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 080import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 081import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 082import org.hl7.fhir.utilities.xhtml.XhtmlNode; 083import org.hl7.fhir.utilities.xml.SchematronWriter; 084import org.hl7.fhir.utilities.xml.SchematronWriter.Rule; 085import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType; 086import org.hl7.fhir.utilities.xml.SchematronWriter.Section; 087 088/** 089 * This class provides a set of utility operations for working with Profiles. 090 * Key functionality: 091 * * getChildMap --? 092 * * getChildList 093 * * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 094 * * closeDifferential: fill out a differential by excluding anything not mentioned 095 * * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions 096 * * generateTable: generate the HTML for a hierarchical table presentation of a structure 097 * * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point 098 * * summarize: describe the contents of a profile 099 * 100 * note to maintainers: Do not make modifications to the snapshot generation without first changing the snapshot generation test cases to demonstrate the grounds for your change 101 * 102 * @author Grahame 103 * 104 */ 105public class ProfileUtilities extends TranslatingUtilities { 106 107 private static int nextSliceId = 0; 108 109 public class ExtensionContext { 110 111 private ElementDefinition element; 112 private StructureDefinition defn; 113 114 public ExtensionContext(StructureDefinition ext, ElementDefinition ed) { 115 this.defn = ext; 116 this.element = ed; 117 } 118 119 public ElementDefinition getElement() { 120 return element; 121 } 122 123 public StructureDefinition getDefn() { 124 return defn; 125 } 126 127 public String getUrl() { 128 if (element == defn.getSnapshot().getElement().get(0)) 129 return defn.getUrl(); 130 else 131 return element.getSliceName(); 132 } 133 134 public ElementDefinition getExtensionValueDefinition() { 135 int i = defn.getSnapshot().getElement().indexOf(element)+1; 136 while (i < defn.getSnapshot().getElement().size()) { 137 ElementDefinition ed = defn.getSnapshot().getElement().get(i); 138 if (ed.getPath().equals(element.getPath())) 139 return null; 140 if (ed.getPath().startsWith(element.getPath()+".value")) 141 return ed; 142 i++; 143 } 144 return null; 145 } 146 147 } 148 149 private static final String ROW_COLOR_ERROR = "#ffcccc"; 150 private static final String ROW_COLOR_FATAL = "#ff9999"; 151 private static final String ROW_COLOR_WARNING = "#ffebcc"; 152 private static final String ROW_COLOR_HINT = "#ebf5ff"; 153 private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8"; 154 public static final int STATUS_OK = 0; 155 public static final int STATUS_HINT = 1; 156 public static final int STATUS_WARNING = 2; 157 public static final int STATUS_ERROR = 3; 158 public static final int STATUS_FATAL = 4; 159 160 161 private static final String DERIVATION_EQUALS = "derivation.equals"; 162 public static final String DERIVATION_POINTER = "derived.pointer"; 163 public static final String IS_DERIVED = "derived.fact"; 164 public static final String UD_ERROR_STATUS = "error-status"; 165 private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed"; 166 private static final boolean DEBUG = false; 167 168 // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here 169 private final IWorkerContext context; 170 private List<ValidationMessage> messages; 171 private List<String> snapshotStack = new ArrayList<String>(); 172 private ProfileKnowledgeProvider pkp; 173 private boolean igmode; 174 private boolean exception; 175 176 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) { 177 super(); 178 this.context = context; 179 this.messages = messages; 180 this.pkp = pkp; 181 } 182 183 private class UnusedTracker { 184 private boolean used; 185 } 186 187 public boolean isIgmode() { 188 return igmode; 189 } 190 191 192 public void setIgmode(boolean igmode) { 193 this.igmode = igmode; 194 } 195 196 public interface ProfileKnowledgeProvider { 197 public class BindingResolution { 198 public String display; 199 public String url; 200 } 201 boolean isDatatype(String typeSimple); 202 boolean isResource(String typeSimple); 203 boolean hasLinkFor(String typeSimple); 204 String getLinkFor(String corePath, String typeSimple); 205 BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path) throws FHIRException; 206 String getLinkForProfile(StructureDefinition profile, String url); 207 boolean prependLinks(); 208 } 209 210 211 212 public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException { 213 if (element.getContentReference()!=null) { 214 for (ElementDefinition e : profile.getSnapshot().getElement()) { 215 if (element.getContentReference().equals("#"+e.getId())) 216 return getChildMap(profile, e); 217 } 218 throw new DefinitionException("Unable to resolve name reference "+element.getContentReference()+" at path "+element.getPath()); 219 220 } else { 221 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 222 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 223 String path = element.getPath(); 224 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 225 ElementDefinition e = elements.get(index); 226 if (e.getPath().startsWith(path + ".")) { 227 // We only want direct children, not all descendants 228 if (!e.getPath().substring(path.length()+1).contains(".")) 229 res.add(e); 230 } else 231 break; 232 } 233 return res; 234 } 235 } 236 237 238 public static List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException { 239 if (!element.hasSlicing()) 240 throw new Error("getSliceList should only be called when the element has slicing"); 241 242 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 243 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 244 String path = element.getPath(); 245 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 246 ElementDefinition e = elements.get(index); 247 if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) { 248 // We want elements with the same path (until we hit an element that doesn't start with the same path) 249 if (e.getPath().equals(element.getPath())) 250 res.add(e); 251 } else 252 break; 253 } 254 return res; 255 } 256 257 258 /** 259 * Given a Structure, navigate to the element given by the path and return the direct children of that element 260 * 261 * @param structure The structure to navigate into 262 * @param path The path of the element within the structure to get the children for 263 * @return A List containing the element children (all of them are Elements) 264 */ 265 public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) { 266 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 267 268 boolean capturing = id==null; 269 if (id==null && !path.contains(".")) 270 capturing = true; 271 272 for (ElementDefinition e : profile.getSnapshot().getElement()) { 273 if (e == null) 274 throw new Error("element = null: "+profile.getUrl()); 275 if (e.getId() == null) 276 throw new Error("element id = null: "+e.toString()+" on "+profile.getUrl()); 277 278 if (!capturing && id!=null && e.getId().equals(id)) { 279 capturing = true; 280 } 281 282 // If our element is a slice, stop capturing children as soon as we see the next slice 283 if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path)) 284 break; 285 286 if (capturing) { 287 String p = e.getPath(); 288 289 if (!Utilities.noString(e.getContentReference()) && path.startsWith(p)) { 290 if (path.length() > p.length()) 291 return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null); 292 else 293 return getChildList(profile, e.getContentReference(), null); 294 295 } else if (p.startsWith(path+".") && !p.equals(path)) { 296 String tail = p.substring(path.length()+1); 297 if (!tail.contains(".")) { 298 res.add(e); 299 } 300 } 301 } 302 } 303 304 return res; 305 } 306 307 308 public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) { 309 return getChildList(structure, element.getPath(), element.getId()); 310 } 311 312 public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException { 313 if (base == null) 314 throw new DefinitionException("no base profile provided"); 315 if (derived == null) 316 throw new DefinitionException("no derived structure provided"); 317 318 for (StructureDefinitionMappingComponent baseMap : base.getMapping()) { 319 boolean found = false; 320 for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) { 321 if (derivedMap.getUri().equals(baseMap.getUri())) { 322 found = true; 323 break; 324 } 325 } 326 if (!found) 327 derived.getMapping().add(baseMap); 328 } 329 } 330 331 /** 332 * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 333 * 334 * @param base - the base structure on which the differential will be applied 335 * @param differential - the differential to apply to the base 336 * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL 337 * @param trimDifferential - if this is true, then the snap short generator will remove any material in the element definitions that is not different to the base 338 * @return 339 * @throws FHIRException 340 * @throws DefinitionException 341 * @throws Exception 342 */ 343 public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String profileName) throws DefinitionException, FHIRException { 344 if (base == null) 345 throw new DefinitionException("no base profile provided"); 346 if (derived == null) 347 throw new DefinitionException("no derived structure provided"); 348 349 if (snapshotStack.contains(derived.getUrl())) 350 throw new DefinitionException("Circular snapshot references detected; cannot generate snapshot (stack = "+snapshotStack.toString()+")"); 351 snapshotStack.add(derived.getUrl()); 352 353 354 derived.setSnapshot(new StructureDefinitionSnapshotComponent()); 355 356 357 // so we have two lists - the base list, and the differential list 358 // the differential list is only allowed to include things that are in the base list, but 359 // is allowed to include them multiple times - thereby slicing them 360 361 // our approach is to walk through the base list, and see whether the differential 362 // says anything about them. 363 int baseCursor = 0; 364 int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths 365 366 if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) 367 throw new Error("type on first differential element!"); 368 369 for (ElementDefinition e : derived.getDifferential().getElement()) 370 e.clearUserData(GENERATED_IN_SNAPSHOT); 371 372 // we actually delegate the work to a subroutine so we can re-enter it with a different cursors 373 374 processPaths("", derived.getSnapshot(), base.getSnapshot(), derived.getDifferential(), baseCursor, diffCursor, base.getSnapshot().getElement().size()-1, 375 derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, derived.getId(), null, null, false, base.getUrl(), null, false, null); 376 if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty()) 377 throw new Error("type on first snapshot element for "+derived.getSnapshot().getElementFirstRep().getPath()+" in "+derived.getUrl()+" from "+base.getUrl()); 378 updateMaps(base, derived); 379 setIds(derived, false); 380 381 if (DEBUG) { 382 System.out.println("Differential: "); 383 for (ElementDefinition ed : derived.getDifferential().getElement()) 384 System.out.println(" "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" id = "+ed.getId()+" "+constraintSummary(ed)); 385 System.out.println("Snapshot: "); 386 for (ElementDefinition ed : derived.getSnapshot().getElement()) 387 System.out.println(" "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" id = "+ed.getId()+" "+constraintSummary(ed)); 388 } 389 //Check that all differential elements have a corresponding snapshot element 390 for (ElementDefinition e : derived.getDifferential().getElement()) { 391 if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) { 392 System.out.println("Error in snapshot generation: Differential for "+derived.getUrl()+" with id: " + e.getId()+" has an element that is not marked with a snapshot match"); 393 if (exception) 394 throw new DefinitionException("Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has id: " + e.getId()); 395 else 396 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, "Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has id: " + e.getId(), ValidationMessage.IssueSeverity.ERROR)); 397 } 398 } 399 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 400 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 401 if (!ed.hasBase()) { 402 ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax()); 403 } 404 } 405 } 406 } 407 408 private String constraintSummary(ElementDefinition ed) { 409 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 410 if (ed.hasPattern()) 411 b.append("pattern="+ed.getPattern().fhirType()); 412 if (ed.hasFixed()) 413 b.append("fixed="+ed.getFixed().fhirType()); 414 if (ed.hasConstraint()) 415 b.append("constraints="+ed.getConstraint().size()); 416 return b.toString(); 417 } 418 419 420 private String sliceSummary(ElementDefinition ed) { 421 if (!ed.hasSlicing() && !ed.hasSliceName()) 422 return ""; 423 if (ed.hasSliceName()) 424 return " (slicename = "+ed.getSliceName()+")"; 425 426 StringBuilder b = new StringBuilder(); 427 boolean first = true; 428 for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) { 429 if (first) 430 first = false; 431 else 432 b.append("|"); 433 b.append(d.getPath()); 434 } 435 return " (slicing by "+b.toString()+")"; 436 } 437 438 439 private String typeSummary(ElementDefinition ed) { 440 StringBuilder b = new StringBuilder(); 441 boolean first = true; 442 for (TypeRefComponent tr : ed.getType()) { 443 if (first) 444 first = false; 445 else 446 b.append("|"); 447 b.append(tr.getCode()); 448 } 449 return b.toString(); 450 } 451 452 private String typeSummaryWithProfile(ElementDefinition ed) { 453 StringBuilder b = new StringBuilder(); 454 boolean first = true; 455 for (TypeRefComponent tr : ed.getType()) { 456 if (first) 457 first = false; 458 else 459 b.append("|"); 460 b.append(tr.getCode()); 461 if (tr.hasProfile()) { 462 b.append("("); 463 b.append(tr.getProfile()); 464 b.append(")"); 465 466 } 467 } 468 return b.toString(); 469 } 470 471 472 private boolean findMatchingElement(String id, List<ElementDefinition> list) { 473 for (ElementDefinition ed : list) { 474 if (ed.getId().equals(id)) 475 return true; 476 if (id.endsWith("[x]")) { 477 if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains(".")) 478 return true; 479 } 480 } 481 return false; 482 } 483 484 485 /** 486 * @param trimDifferential 487 * @throws DefinitionException, FHIRException 488 * @throws Exception 489 */ 490 private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit, 491 int diffLimit, String url, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone, ElementDefinition redirector) throws DefinitionException, FHIRException { 492 if (DEBUG) 493 System.out.println(indent+"PP @ "+resultPathBase+": base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+")"); 494 ElementDefinition res = null; 495 // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries) 496 while (baseCursor <= baseLimit) { 497 // get the current focus of the base, and decide what to do 498 ElementDefinition currentBase = base.getElement().get(baseCursor); 499 String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector); 500 if (DEBUG) 501 System.out.println(indent+" - "+cpath+": base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicingDone = "+slicingDone+")"); 502 List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName, url); // get a list of matching elements in scope 503 504 // in the simple case, source is not sliced. 505 if (!currentBase.hasSlicing()) { 506 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 507 // so we just copy it in 508 ElementDefinition outcome = updateURLs(url, currentBase.copy()); 509 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 510 updateFromBase(outcome, currentBase); 511 markDerived(outcome); 512 if (resultPathBase == null) 513 resultPathBase = outcome.getPath(); 514 else if (!outcome.getPath().startsWith(resultPathBase)) 515 throw new DefinitionException("Adding wrong path"); 516 result.getElement().add(outcome); 517 if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement())) { 518 // well, the profile walks into this, so we need to as well 519 if (outcome.getType().size() > 1) { 520 for (TypeRefComponent t : outcome.getType()) { 521 if (!t.getCode().equals("Reference")) 522 throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName); 523 } 524 } 525 StructureDefinition dt = outcome.getType().isEmpty() ? null : getProfileForDataType(outcome.getType().get(0)); 526 if (dt == null) 527 throw new DefinitionException(cpath+" has children for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type"); 528 contextName = dt.getUrl(); 529 int start = diffCursor; 530 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+".")) 531 diffCursor++; 532 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, 533 diffCursor-1, url, profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null); 534 } 535 baseCursor++; 536 } else if (diffMatches.size() == 1 && (slicingDone || !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName())))) {// one matching element in the differential 537 ElementDefinition template = null; 538 if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 && diffMatches.get(0).getType().get(0).hasProfile() && !diffMatches.get(0).getType().get(0).getCode().equals("Reference")) { 539 String p = diffMatches.get(0).getType().get(0).getProfile().get(0).getValue(); 540 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p); 541 if (sd != null) { 542 if (!sd.hasSnapshot()) { 543 StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 544 if (sdb == null) 545 throw new DefinitionException("no base for "+sd.getBaseDefinition()); 546 generateSnapshot(sdb, sd, sd.getUrl(), sd.getName()); 547 } 548 template = sd.getSnapshot().getElement().get(0).copy().setPath(currentBase.getPath()); 549 template.setSliceName(null); 550 // temporary work around 551 if (!diffMatches.get(0).getType().get(0).getCode().equals("Extension")) { 552 template.setMin(currentBase.getMin()); 553 template.setMax(currentBase.getMax()); 554 } 555 } 556 } 557 if (template == null) 558 template = currentBase.copy(); 559 else 560 // some of what's in currentBase overrides template 561 template = overWriteWithCurrent(template, currentBase); 562 563 ElementDefinition outcome = updateURLs(url, template); 564 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 565 if (res == null) 566 res = outcome; 567 updateFromBase(outcome, currentBase); 568 if (diffMatches.get(0).hasSliceName()) 569 outcome.setSliceName(diffMatches.get(0).getSliceName()); 570 outcome.setSlicing(null); 571 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url); 572 if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*")) // if the base profile allows multiple types, but the profile only allows one, rename it 573 outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode())); 574 if (resultPathBase == null) 575 resultPathBase = outcome.getPath(); 576 else if (!outcome.getPath().startsWith(resultPathBase)) 577 throw new DefinitionException("Adding wrong path"); 578 result.getElement().add(outcome); 579 baseCursor++; 580 diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1; 581 if (differential.getElement().size() > diffCursor && outcome.getPath().contains(".") && (isDataType(outcome.getType()) || outcome.hasContentReference())) { // don't want to do this for the root, since that's base, and we're already processing it 582 if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) { 583 if (outcome.getType().size() > 1) { 584 if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) { 585 String en = tail(outcome.getPath()); 586 String tn = tail(diffMatches.get(0).getPath()); 587 String t = tn.substring(en.length()-3); 588 if (isPrimitive(Utilities.uncapitalize(t))) 589 t = Utilities.uncapitalize(t); 590 List<TypeRefComponent> ntr = getByTypeName(outcome.getType(), t); // keep any additional information 591 if (ntr.isEmpty()) 592 ntr.add(new TypeRefComponent().setCode(t)); 593 outcome.getType().clear(); 594 outcome.getType().addAll(ntr); 595 } 596 if (outcome.getType().size() > 1) 597 for (TypeRefComponent t : outcome.getType()) { 598 if (!t.getCode().equals("Reference")) 599 throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName); 600 } 601 } 602 int start = diffCursor; 603 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) 604 diffCursor++; 605 if (outcome.hasContentReference()) { 606 ElementDefinition tgt = getElementById(base.getElement(), outcome.getContentReference()); 607 if (tgt == null) 608 throw new DefinitionException("Unable to resolve reference to "+outcome.getContentReference()); 609 replaceFromContentReference(outcome, tgt); 610 int nbc = base.getElement().indexOf(tgt)+1; 611 int nbl = nbc; 612 while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getPath()+".")) 613 nbl++; 614 processPaths(indent+" ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, outcome); 615 } else { 616 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 617 if (dt == null) 618 throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type"); 619 contextName = dt.getUrl(); 620 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, 621 diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null); 622 } 623 } 624 } 625 } else { 626 // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct 627 if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0))) 628 // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1 629 // (but you might do that in order to split up constraints by type) 630 throw new DefinitionException("Attempt to a slice an element that does not repeat: "+currentBase.getPath()+"/"+currentBase.getPath()+" from "+contextName+" in "+url); 631 if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error 632 throw new DefinitionException("Differential does not have a slice: "+currentBase.getPath()+"/ (b:"+baseCursor+" of "+ baseLimit+" / "+ diffCursor +"/ "+diffLimit+") in profile "+url); 633 634 // well, if it passed those preconditions then we slice the dest. 635 int start = 0; 636 int nbl = findEndOfElement(base, baseCursor); 637// if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) { 638 if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && (nbl > baseCursor || differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1)) { // there's a default set before the slices 639 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 640 int ndl = findEndOfElement(differential, ndc); 641 processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, null).setSlicing(diffMatches.get(0).getSlicing()); 642 start++; 643 } else { 644 // we're just going to accept the differential slicing at face value 645 ElementDefinition outcome = updateURLs(url, currentBase.copy()); 646 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 647 updateFromBase(outcome, currentBase); 648 649 if (!diffMatches.get(0).hasSlicing()) 650 outcome.setSlicing(makeExtensionSlicing()); 651 else 652 outcome.setSlicing(diffMatches.get(0).getSlicing().copy()); 653 if (!outcome.getPath().startsWith(resultPathBase)) 654 throw new DefinitionException("Adding wrong path"); 655 result.getElement().add(outcome); 656 657 // differential - if the first one in the list has a name, we'll process it. Else we'll treat it as the base definition of the slice. 658 if (!diffMatches.get(0).hasSliceName()) { 659 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url); 660 if (!outcome.hasContentReference() && !outcome.hasType()) { 661 throw new DefinitionException("not done yet"); 662 } 663 start++; 664 // result.getElement().remove(result.getElement().size()-1); 665 } else 666 checkExtensionDoco(outcome); 667 } 668 // now, for each entry in the diff matches, we're going to process the base item 669 // our processing scope for base is all the children of the current path 670 int ndc = diffCursor; 671 int ndl = diffCursor; 672 for (int i = start; i < diffMatches.size(); i++) { 673 // our processing scope for the differential is the item in the list, and all the items before the next one in the list 674 ndc = differential.getElement().indexOf(diffMatches.get(i)); 675 ndl = findEndOfElement(differential, ndc); 676/* if (skipSlicingElement && i == 0) { 677 ndc = ndc + 1; 678 if (ndc > ndl) 679 continue; 680 }*/ 681 // now we process the base scope repeatedly for each instance of the item in the differential list 682 processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, redirector); 683 } 684 // ok, done with that - next in the base list 685 baseCursor = nbl+1; 686 diffCursor = ndl+1; 687 } 688 } else { 689 // the item is already sliced in the base profile. 690 // here's the rules 691 // 1. irrespective of whether the slicing is ordered or not, the definition order must be maintained 692 // 2. slice element names have to match. 693 // 3. new slices must be introduced at the end 694 // corallory: you can't re-slice existing slices. is that ok? 695 696 // we're going to need this: 697 String path = currentBase.getPath(); 698 ElementDefinition original = currentBase; 699 700 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 701 // copy across the currentbase, and all of its children and siblings 702 while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) { 703 ElementDefinition outcome = updateURLs(url, base.getElement().get(baseCursor).copy()); 704 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 705 if (!outcome.getPath().startsWith(resultPathBase)) 706 throw new DefinitionException("Adding wrong path in profile " + profileName + ": "+outcome.getPath()+" vs " + resultPathBase); 707 result.getElement().add(outcome); // so we just copy it in 708 baseCursor++; 709 } 710 } else { 711 // first - check that the slicing is ok 712 boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED; 713 int diffpos = 0; 714 boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension"); 715 if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing 716// if (!isExtension) 717// diffpos++; // if there's a slice on the first, we'll ignore any content it has 718 ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing(); 719 ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing(); 720 if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement())) 721 throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - order @ "+path+" ("+contextName+")"); 722 if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator())) 723 throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - disciminator @ "+path+" ("+contextName+")"); 724 if (!ruleMatches(dSlice.getRules(), bSlice.getRules())) 725 throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - rule @ "+path+" ("+contextName+")"); 726 } 727 ElementDefinition outcome = updateURLs(url, currentBase.copy()); 728 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 729 updateFromBase(outcome, currentBase); 730 if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) { 731 updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing()); 732 updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url); // if there's no slice, we don't want to update the unsliced description 733 } else if (!diffMatches.get(0).hasSliceName()) 734 diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, true); // because of updateFromDefinition isn't called 735 736 result.getElement().add(outcome); 737 738 if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice 739 diffpos++; 740 } 741 if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) { 742 int nbl = findEndOfElement(base, baseCursor); 743 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 744 int ndl = findEndOfElement(differential, ndc); 745 processPaths(indent+" ", result, base, differential, baseCursor+1, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, null); 746// throw new Error("Not done yet"); 747// } else if (currentBase.getType().get(0).getCode().equals("BackboneElement") && diffMatches.size() > 0 && diffMatches.get(0).hasSliceName()) { 748 } else if (currentBase.getType().get(0).getCode().equals("BackboneElement")) { 749 // We need to copy children of the backbone element before we start messing around with slices 750 int nbl = findEndOfElement(base, baseCursor); 751 for (int i = baseCursor+1; i<=nbl; i++) { 752 outcome = updateURLs(url, base.getElement().get(i).copy()); 753 result.getElement().add(outcome); 754 } 755 } 756 757 // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff. 758 List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase); 759 for (ElementDefinition baseItem : baseMatches) { 760 baseCursor = base.getElement().indexOf(baseItem); 761 outcome = updateURLs(url, baseItem.copy()); 762 updateFromBase(outcome, currentBase); 763 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 764 outcome.setSlicing(null); 765 if (!outcome.getPath().startsWith(resultPathBase)) 766 throw new DefinitionException("Adding wrong path"); 767 if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) { 768 // if there's a diff, we update the outcome with diff 769 // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url); 770 //then process any children 771 int nbl = findEndOfElement(base, baseCursor); 772 int ndc = differential.getElement().indexOf(diffMatches.get(diffpos)); 773 int ndl = findEndOfElement(differential, ndc); 774 // now we process the base scope repeatedly for each instance of the item in the differential list 775 processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, resultPathBase, true, null); 776 // ok, done with that - now set the cursors for if this is the end 777 baseCursor = nbl; 778 diffCursor = ndl+1; 779 diffpos++; 780 } else { 781 result.getElement().add(outcome); 782 baseCursor++; 783 // just copy any children on the base 784 while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) { 785 outcome = updateURLs(url, base.getElement().get(baseCursor).copy()); 786 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 787 if (!outcome.getPath().startsWith(resultPathBase)) 788 throw new DefinitionException("Adding wrong path"); 789 result.getElement().add(outcome); 790 baseCursor++; 791 } 792 //Lloyd - add this for test T15 793 baseCursor--; 794 } 795 } 796 // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed 797 if (closed && diffpos < diffMatches.size()) 798 throw new DefinitionException("The base snapshot marks a slicing as closed, but the differential tries to extend it in "+profileName+" at "+path+" ("+cpath+")"); 799 if (diffpos == diffMatches.size()) { 800//Lloyd This was causing problems w/ Telus 801// diffCursor++; 802 } else { 803 while (diffpos < diffMatches.size()) { 804 ElementDefinition diffItem = diffMatches.get(diffpos); 805 for (ElementDefinition baseItem : baseMatches) 806 if (baseItem.getSliceName().equals(diffItem.getSliceName())) 807 throw new DefinitionException("Named items are out of order in the slice"); 808 outcome = updateURLs(url, currentBase.copy()); 809 // outcome = updateURLs(url, diffItem.copy()); 810 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 811 updateFromBase(outcome, currentBase); 812 outcome.setSlicing(null); 813 if (!outcome.getPath().startsWith(resultPathBase)) 814 throw new DefinitionException("Adding wrong path"); 815 result.getElement().add(outcome); 816 updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url); 817 // --- LM Added this 818 diffCursor = differential.getElement().indexOf(diffItem)+1; 819 if (!outcome.getType().isEmpty() && (/*outcome.getType().get(0).getCode().equals("Extension") || */differential.getElement().size() > diffCursor) && outcome.getPath().contains(".") && isDataType(outcome.getType())) { // don't want to do this for the root, since that's base, and we're already processing it 820 if (!baseWalksInto(base.getElement(), baseCursor)) { 821 if (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) { 822 if (outcome.getType().size() > 1) 823 for (TypeRefComponent t : outcome.getType()) { 824 if (!t.getCode().equals("Reference")) 825 throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName); 826 } 827 TypeRefComponent t = outcome.getType().get(0); 828 if (t.getCode().equals("BackboneElement")) { 829 int baseStart = base.getElement().indexOf(currentBase)+1; 830 int baseMax = baseStart + 1; 831 while (baseMax < base.getElement().size() && base.getElement().get(baseMax).getPath().startsWith(currentBase.getPath()+".")) 832 baseMax++; 833 int start = diffCursor; 834 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) 835 diffCursor++; 836 processPaths(indent+" ", result, base, differential, baseStart, start-1, baseMax-1, 837 diffCursor - 1, url, profileName+pathTail(diffMatches, 0), base.getElement().get(0).getPath(), base.getElement().get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null); 838 839 } else { 840 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 841 // if (t.getCode().equals("Extension") && t.hasProfile() && !t.getProfile().contains(":")) { 842 // lloydfix dt = 843 // } 844 if (dt == null) 845 throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type"); 846 contextName = dt.getUrl(); 847 int start = diffCursor; 848 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) 849 diffCursor++; 850 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1, 851 diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null); 852 } 853 } else if (outcome.getType().get(0).getCode().equals("Extension")) { 854 // Force URL to appear if we're dealing with an extension. (This is a kludge - may need to drill down in other cases where we're slicing and the type has a profile declaration that could be setting the fixed value) 855 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 856 for (ElementDefinition extEd : dt.getSnapshot().getElement()) { 857 // We only want the children that aren't the root 858 if (extEd.getPath().contains(".")) { 859 ElementDefinition extUrlEd = updateURLs(url, extEd.copy()); 860 extUrlEd.setPath(fixedPathDest(outcome.getPath(), extUrlEd.getPath(), null, null)); 861 // updateFromBase(extUrlEd, currentBase); 862 markDerived(extUrlEd); 863 result.getElement().add(extUrlEd); 864 } 865 } 866 } 867 } 868 } 869 // --- 870 diffpos++; 871 } 872 } 873 baseCursor++; 874 } 875 } 876 } 877 878 int i = 0; 879 for (ElementDefinition e : result.getElement()) { 880 i++; 881 if (e.hasMinElement() && e.getMinElement().getValue()==null) 882 throw new Error("null min"); 883 } 884 return res; 885 } 886 887 888 private List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) { 889 List<TypeRefComponent> res = new ArrayList<TypeRefComponent>(); 890 for (TypeRefComponent tr : type) { 891 if (t.equals(tr.getCode())) 892 res.add(tr); 893 } 894 return res; 895 } 896 897 898 private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) { 899 outcome.setContentReference(null); 900 outcome.getType().clear(); // though it should be clear anyway 901 outcome.getType().addAll(tgt.getType()); 902 } 903 904 905 private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) { 906 if (cursor >= elements.size()) 907 return false; 908 String path = elements.get(cursor).getPath(); 909 String prevPath = elements.get(cursor - 1).getPath(); 910 return path.startsWith(prevPath + "."); 911 } 912 913 914 private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError { 915 ElementDefinition res = profile.copy(); 916 if (usage.hasSliceName()) 917 res.setSliceName(usage.getSliceName()); 918 if (usage.hasLabel()) 919 res.setLabel(usage.getLabel()); 920 for (Coding c : usage.getCode()) 921 res.addCode(c); 922 923 if (usage.hasDefinition()) 924 res.setDefinition(usage.getDefinition()); 925 if (usage.hasShort()) 926 res.setShort(usage.getShort()); 927 if (usage.hasComment()) 928 res.setComment(usage.getComment()); 929 if (usage.hasRequirements()) 930 res.setRequirements(usage.getRequirements()); 931 for (StringType c : usage.getAlias()) 932 res.addAlias(c.getValue()); 933 if (usage.hasMin()) 934 res.setMin(usage.getMin()); 935 if (usage.hasMax()) 936 res.setMax(usage.getMax()); 937 938 if (usage.hasFixed()) 939 res.setFixed(usage.getFixed()); 940 if (usage.hasPattern()) 941 res.setPattern(usage.getPattern()); 942 if (usage.hasExample()) 943 res.setExample(usage.getExample()); 944 if (usage.hasMinValue()) 945 res.setMinValue(usage.getMinValue()); 946 if (usage.hasMaxValue()) 947 res.setMaxValue(usage.getMaxValue()); 948 if (usage.hasMaxLength()) 949 res.setMaxLength(usage.getMaxLength()); 950 if (usage.hasMustSupport()) 951 res.setMustSupport(usage.getMustSupport()); 952 if (usage.hasBinding()) 953 res.setBinding(usage.getBinding().copy()); 954 for (ElementDefinitionConstraintComponent c : usage.getConstraint()) 955 res.addConstraint(c); 956 for (Extension e : usage.getExtension()) { 957 if (!res.hasExtension(e.getUrl())) 958 res.addExtension(e.copy()); 959 } 960 961 return res; 962 } 963 964 965 private boolean checkExtensionDoco(ElementDefinition base) { 966 // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff 967 boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension"); 968 if (isExtension) { 969 base.setDefinition("An Extension"); 970 base.setShort("Extension"); 971 base.setCommentElement(null); 972 base.setRequirementsElement(null); 973 base.getAlias().clear(); 974 base.getMapping().clear(); 975 } 976 return isExtension; 977 } 978 979 980 private String pathTail(List<ElementDefinition> diffMatches, int i) { 981 982 ElementDefinition d = diffMatches.get(i); 983 String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath(); 984 return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : ""); 985 } 986 987 988 private void markDerived(ElementDefinition outcome) { 989 for (ElementDefinitionConstraintComponent inv : outcome.getConstraint()) 990 inv.setUserData(IS_DERIVED, true); 991 } 992 993 994 private String summarizeSlicing(ElementDefinitionSlicingComponent slice) { 995 StringBuilder b = new StringBuilder(); 996 boolean first = true; 997 for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) { 998 if (first) 999 first = false; 1000 else 1001 b.append(", "); 1002 b.append(d); 1003 } 1004 b.append("("); 1005 if (slice.hasOrdered()) 1006 b.append(slice.getOrderedElement().asStringValue()); 1007 b.append("/"); 1008 if (slice.hasRules()) 1009 b.append(slice.getRules().toCode()); 1010 b.append(")"); 1011 if (slice.hasDescription()) { 1012 b.append(" \""); 1013 b.append(slice.getDescription()); 1014 b.append("\""); 1015 } 1016 return b.toString(); 1017 } 1018 1019 1020 private void updateFromBase(ElementDefinition derived, ElementDefinition base) { 1021 if (base.hasBase()) { 1022 if (!derived.hasBase()) 1023 derived.setBase(new ElementDefinitionBaseComponent()); 1024 derived.getBase().setPath(base.getBase().getPath()); 1025 derived.getBase().setMin(base.getBase().getMin()); 1026 derived.getBase().setMax(base.getBase().getMax()); 1027 } else { 1028 if (!derived.hasBase()) 1029 derived.setBase(new ElementDefinitionBaseComponent()); 1030 derived.getBase().setPath(base.getPath()); 1031 derived.getBase().setMin(base.getMin()); 1032 derived.getBase().setMax(base.getMax()); 1033 } 1034 } 1035 1036 1037 private boolean pathStartsWith(String p1, String p2) { 1038 return p1.startsWith(p2); 1039 } 1040 1041 private boolean pathMatches(String p1, String p2) { 1042 return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains(".")); 1043 } 1044 1045 1046 private String fixedPathSource(String contextPath, String pathSimple, ElementDefinition redirector) { 1047 if (contextPath == null) 1048 return pathSimple; 1049// String ptail = pathSimple.substring(contextPath.length() + 1); 1050 if (redirector != null) { 1051 String ptail = pathSimple.substring(contextPath.length()+1); 1052 return redirector.getPath()+"."+ptail; 1053// return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1); 1054 } else { 1055 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1056 return contextPath+"."+ptail; 1057 } 1058 } 1059 1060 private String fixedPathDest(String contextPath, String pathSimple, ElementDefinition redirector, String redirectSource) { 1061 String s; 1062 if (contextPath == null) 1063 s = pathSimple; 1064 else { 1065 if (redirector != null) { 1066 String ptail = pathSimple.substring(redirectSource.length() + 1); 1067 // ptail = ptail.substring(ptail.indexOf(".")+1); 1068 s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail; 1069 } else { 1070 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1071 s = contextPath+"."+ptail; 1072 } 1073 } 1074 return s; 1075 } 1076 1077 private StructureDefinition getProfileForDataType(TypeRefComponent type) { 1078 StructureDefinition sd = null; 1079 if (type.hasProfile()) { 1080 sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue()); 1081 if (sd == null) 1082 System.out.println("Failed to find referenced profile: " + type.getProfile()); 1083 } 1084 if (sd == null) 1085 sd = context.fetchTypeDefinition(type.getCode()); 1086 if (sd == null) 1087 System.out.println("XX: failed to find profle for type: " + type.getCode()); // debug GJM 1088 return sd; 1089 } 1090 1091 1092 public static String typeCode(List<TypeRefComponent> types) { 1093 StringBuilder b = new StringBuilder(); 1094 boolean first = true; 1095 for (TypeRefComponent type : types) { 1096 if (first) first = false; else b.append(", "); 1097 b.append(type.getCode()); 1098 if (type.hasTargetProfile()) 1099 b.append("{"+type.getTargetProfile()+"}"); 1100 else if (type.hasProfile()) 1101 b.append("{"+type.getProfile()+"}"); 1102 } 1103 return b.toString(); 1104 } 1105 1106 1107 private boolean isDataType(List<TypeRefComponent> types) { 1108 if (types.isEmpty()) 1109 return false; 1110 for (TypeRefComponent type : types) { 1111 String t = type.getCode(); 1112 if (!isDataType(t) && !isPrimitive(t)) 1113 return false; 1114 } 1115 return true; 1116 } 1117 1118 1119 /** 1120 * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url 1121 * @param url - the base url to use to turn internal references into absolute references 1122 * @param element - the Element to update 1123 * @return - the updated Element 1124 */ 1125 private ElementDefinition updateURLs(String url, ElementDefinition element) { 1126 if (element != null) { 1127 ElementDefinition defn = element; 1128 if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#")) 1129 defn.getBinding().setValueSet(url+defn.getBinding().getValueSet()); 1130 for (TypeRefComponent t : defn.getType()) { 1131 for (UriType u : t.getProfile()) { 1132 if (u.getValue().startsWith("#")) 1133 u.setValue(url+t.getProfile()); 1134 } 1135 for (UriType u : t.getTargetProfile()) { 1136 if (u.getValue().startsWith("#")) 1137 u.setValue(url+t.getTargetProfile()); 1138 } 1139 } 1140 } 1141 return element; 1142 } 1143 1144 private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) { 1145 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1146 String path = current.getPath(); 1147 int cursor = list.indexOf(current)+1; 1148 while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) { 1149 if (pathMatches(list.get(cursor).getPath(), path)) 1150 result.add(list.get(cursor)); 1151 cursor++; 1152 } 1153 return result; 1154 } 1155 1156 private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) { 1157 if (src.hasOrderedElement()) 1158 dst.setOrderedElement(src.getOrderedElement().copy()); 1159 if (src.hasDiscriminator()) { 1160 // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll because it uses object equality, not string equality 1161 for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) { 1162 boolean found = false; 1163 for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) { 1164 if (matches(d, s)) { 1165 found = true; 1166 break; 1167 } 1168 } 1169 if (!found) 1170 dst.getDiscriminator().add(s); 1171 } 1172 } 1173 if (src.hasRulesElement()) 1174 dst.setRulesElement(src.getRulesElement().copy()); 1175 } 1176 1177 private boolean orderMatches(BooleanType diff, BooleanType base) { 1178 return (diff == null) || (base == null) || (diff.getValue() == base.getValue()); 1179 } 1180 1181 private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) { 1182 if (diff.isEmpty() || base.isEmpty()) 1183 return true; 1184 if (diff.size() != base.size()) 1185 return false; 1186 for (int i = 0; i < diff.size(); i++) 1187 if (!matches(diff.get(i), base.get(i))) 1188 return false; 1189 return true; 1190 } 1191 1192 private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) { 1193 return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath()); 1194 } 1195 1196 1197 private boolean ruleMatches(SlicingRules diff, SlicingRules base) { 1198 return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) || 1199 ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED)); 1200 } 1201 1202 private boolean isSlicedToOneOnly(ElementDefinition e) { 1203 return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); 1204 } 1205 1206 private ElementDefinitionSlicingComponent makeExtensionSlicing() { 1207 ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); 1208 nextSliceId++; 1209 slice.setId(Integer.toString(nextSliceId)); 1210 slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); 1211 slice.setOrdered(false); 1212 slice.setRules(SlicingRules.OPEN); 1213 return slice; 1214 } 1215 1216 private boolean isExtension(ElementDefinition currentBase) { 1217 return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension"); 1218 } 1219 1220 private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base) throws DefinitionException { 1221 for (int i = start; i <= end; i++) { 1222 String statedPath = context.getElement().get(i).getPath(); 1223 if (statedPath.startsWith(path+".") && !statedPath.substring(path.length()+1).contains(".")) { 1224 boolean found = false; 1225 for (ElementDefinition ed : base) { 1226 String ep = ed.getPath(); 1227 if (ep.equals(statedPath) || (ep.endsWith("[x]") && statedPath.length() > ep.length() - 2 && statedPath.substring(0, ep.length()-3).equals(ep.substring(0, ep.length()-3)) && !statedPath.substring(ep.length()).contains("."))) 1228 found = true; 1229 } 1230 if (!found) 1231 return true; 1232 } 1233 } 1234 return false; 1235 } 1236 1237 private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName, String url) throws DefinitionException { 1238 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1239 for (int i = start; i <= end; i++) { 1240 String statedPath = context.getElement().get(i).getPath(); 1241 if (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 && statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) && (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains(".")))) { 1242 /* 1243 * Commenting this out because it raises warnings when profiling inherited elements. For example, 1244 * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry') 1245 * Not sure we have enough information here to do the check properly. Might be better done when we're sorting the profile? 1246 1247 if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath())) 1248 messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.VALUE, "StructureDefinition.differential.element["+Integer.toString(start)+"]", "Error: unknown element '"+context.getElement().get(start).getPath()+"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')", IssueSeverity.WARNING)); 1249 1250 */ 1251 result.add(context.getElement().get(i)); 1252 } 1253 } 1254 return result; 1255 } 1256 1257 private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) { 1258 int result = cursor; 1259 String path = context.getElement().get(cursor).getPath()+"."; 1260 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 1261 result++; 1262 return result; 1263 } 1264 1265 private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) { 1266 int result = cursor; 1267 String path = context.getElement().get(cursor).getPath()+"."; 1268 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 1269 result++; 1270 return result; 1271 } 1272 1273 private boolean unbounded(ElementDefinition definition) { 1274 StringType max = definition.getMaxElement(); 1275 if (max == null) 1276 return false; // this is not valid 1277 if (max.getValue().equals("1")) 1278 return false; 1279 if (max.getValue().equals("0")) 1280 return false; 1281 return true; 1282 } 1283 1284 private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl) throws DefinitionException, FHIRException { 1285 source.setUserData(GENERATED_IN_SNAPSHOT, true); 1286 // we start with a clone of the base profile ('dest') and we copy from the profile ('source') 1287 // over the top for anything the source has 1288 ElementDefinition base = dest; 1289 ElementDefinition derived = source; 1290 derived.setUserData(DERIVATION_POINTER, base); 1291 boolean isExtension = checkExtensionDoco(base); 1292 1293 1294 // Before applying changes, apply them to what's in the profile 1295 // TODO: follow Chris's rules - Done by Lloyd 1296 StructureDefinition profile = null; 1297 if (base.hasSliceName()) 1298 profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue()) : null; 1299 if (profile==null) 1300 profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue()) : null; 1301 if (profile != null) { 1302 ElementDefinition e = profile.getSnapshot().getElement().get(0); 1303 base.setDefinition(e.getDefinition()); 1304 base.setShort(e.getShort()); 1305 if (e.hasCommentElement()) 1306 base.setCommentElement(e.getCommentElement()); 1307 if (e.hasRequirementsElement()) 1308 base.setRequirementsElement(e.getRequirementsElement()); 1309 base.getAlias().clear(); 1310 base.getAlias().addAll(e.getAlias()); 1311 base.getMapping().clear(); 1312 base.getMapping().addAll(e.getMapping()); 1313 } 1314 if (derived != null) { 1315 if (derived.hasSliceName()) { 1316 base.setSliceName(derived.getSliceName()); 1317 } 1318 1319 if (derived.hasShortElement()) { 1320 if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false)) 1321 base.setShortElement(derived.getShortElement().copy()); 1322 else if (trimDifferential) 1323 derived.setShortElement(null); 1324 else if (derived.hasShortElement()) 1325 derived.getShortElement().setUserData(DERIVATION_EQUALS, true); 1326 } 1327 1328 if (derived.hasDefinitionElement()) { 1329 if (derived.getDefinition().startsWith("...")) 1330 base.setDefinition(base.getDefinition()+"\r\n"+derived.getDefinition().substring(3)); 1331 else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) 1332 base.setDefinitionElement(derived.getDefinitionElement().copy()); 1333 else if (trimDifferential) 1334 derived.setDefinitionElement(null); 1335 else if (derived.hasDefinitionElement()) 1336 derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true); 1337 } 1338 1339 if (derived.hasCommentElement()) { 1340 if (derived.getComment().startsWith("...")) 1341 base.setComment(base.getComment()+"\r\n"+derived.getComment().substring(3)); 1342 else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false)) 1343 base.setCommentElement(derived.getCommentElement().copy()); 1344 else if (trimDifferential) 1345 base.setCommentElement(derived.getCommentElement().copy()); 1346 else if (derived.hasCommentElement()) 1347 derived.getCommentElement().setUserData(DERIVATION_EQUALS, true); 1348 } 1349 1350 if (derived.hasLabelElement()) { 1351 if (derived.getLabel().startsWith("...")) 1352 base.setLabel(base.getLabel()+"\r\n"+derived.getLabel().substring(3)); 1353 else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false)) 1354 base.setLabelElement(derived.getLabelElement().copy()); 1355 else if (trimDifferential) 1356 base.setLabelElement(derived.getLabelElement().copy()); 1357 else if (derived.hasLabelElement()) 1358 derived.getLabelElement().setUserData(DERIVATION_EQUALS, true); 1359 } 1360 1361 if (derived.hasRequirementsElement()) { 1362 if (derived.getRequirements().startsWith("...")) 1363 base.setRequirements(base.getRequirements()+"\r\n"+derived.getRequirements().substring(3)); 1364 else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false)) 1365 base.setRequirementsElement(derived.getRequirementsElement().copy()); 1366 else if (trimDifferential) 1367 base.setRequirementsElement(derived.getRequirementsElement().copy()); 1368 else if (derived.hasRequirementsElement()) 1369 derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true); 1370 } 1371 // sdf-9 1372 if (derived.hasRequirements() && !base.getPath().contains(".")) 1373 derived.setRequirements(null); 1374 if (base.hasRequirements() && !base.getPath().contains(".")) 1375 base.setRequirements(null); 1376 1377 if (derived.hasAlias()) { 1378 if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false)) 1379 for (StringType s : derived.getAlias()) { 1380 if (!base.hasAlias(s.getValue())) 1381 base.getAlias().add(s.copy()); 1382 } 1383 else if (trimDifferential) 1384 derived.getAlias().clear(); 1385 else 1386 for (StringType t : derived.getAlias()) 1387 t.setUserData(DERIVATION_EQUALS, true); 1388 } 1389 1390 if (derived.hasMinElement()) { 1391 if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { 1392 if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply 1393 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", ValidationMessage.IssueSeverity.ERROR)); 1394 base.setMinElement(derived.getMinElement().copy()); 1395 } else if (trimDifferential) 1396 derived.setMinElement(null); 1397 else 1398 derived.getMinElement().setUserData(DERIVATION_EQUALS, true); 1399 } 1400 1401 if (derived.hasMaxElement()) { 1402 if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { 1403 if (isLargerMax(derived.getMax(), base.getMax())) 1404 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR)); 1405 base.setMaxElement(derived.getMaxElement().copy()); 1406 } else if (trimDifferential) 1407 derived.setMaxElement(null); 1408 else 1409 derived.getMaxElement().setUserData(DERIVATION_EQUALS, true); 1410 } 1411 1412 if (derived.hasFixed()) { 1413 if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) { 1414 base.setFixed(derived.getFixed().copy()); 1415 } else if (trimDifferential) 1416 derived.setFixed(null); 1417 else 1418 derived.getFixed().setUserData(DERIVATION_EQUALS, true); 1419 } 1420 1421 if (derived.hasPattern()) { 1422 if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) { 1423 base.setPattern(derived.getPattern().copy()); 1424 } else 1425 if (trimDifferential) 1426 derived.setPattern(null); 1427 else 1428 derived.getPattern().setUserData(DERIVATION_EQUALS, true); 1429 } 1430 1431 for (ElementDefinitionExampleComponent ex : derived.getExample()) { 1432 boolean found = false; 1433 for (ElementDefinitionExampleComponent exS : base.getExample()) 1434 if (Base.compareDeep(ex, exS, false)) 1435 found = true; 1436 if (!found) 1437 base.addExample(ex.copy()); 1438 else if (trimDifferential) 1439 derived.getExample().remove(ex); 1440 else 1441 ex.setUserData(DERIVATION_EQUALS, true); 1442 } 1443 1444 if (derived.hasMaxLengthElement()) { 1445 if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false)) 1446 base.setMaxLengthElement(derived.getMaxLengthElement().copy()); 1447 else if (trimDifferential) 1448 derived.setMaxLengthElement(null); 1449 else 1450 derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true); 1451 } 1452 1453 // todo: what to do about conditions? 1454 // condition : id 0..* 1455 1456 if (derived.hasMustSupportElement()) { 1457 if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))) 1458 base.setMustSupportElement(derived.getMustSupportElement().copy()); 1459 else if (trimDifferential) 1460 derived.setMustSupportElement(null); 1461 else 1462 derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true); 1463 } 1464 1465 1466 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 1467 // but extensions can change isModifier 1468 if (isExtension) { 1469 if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) 1470 base.setIsModifierElement(derived.getIsModifierElement().copy()); 1471 else if (trimDifferential) 1472 derived.setIsModifierElement(null); 1473 else if (derived.hasIsModifierElement()) 1474 derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true); 1475 if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) 1476 base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy()); 1477 else if (trimDifferential) 1478 derived.setIsModifierReasonElement(null); 1479 else if (derived.hasIsModifierReasonElement()) 1480 derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true); 1481 } 1482 1483 if (derived.hasBinding()) { 1484 if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 1485 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 1486 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change the binding on "+derived.getPath()+" from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), ValidationMessage.IssueSeverity.ERROR)); 1487// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 1488 else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) { 1489 ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet()); 1490 ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet()); 1491 if (baseVs == null) { 1492 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 1493 } else if (contextVs == null) { 1494 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 1495 } else { 1496 ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false); 1497 ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false); 1498 if (expBase.getValueset() == null) 1499 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 1500 else if (expDerived.getValueset() == null) 1501 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 1502 else if (!isSubset(expBase.getValueset(), expDerived.getValueset())) 1503 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR)); 1504 1505 } 1506 } 1507 base.setBinding(derived.getBinding().copy()); 1508 } else if (trimDifferential) 1509 derived.setBinding(null); 1510 else 1511 derived.getBinding().setUserData(DERIVATION_EQUALS, true); 1512 } // else if (base.hasBinding() && doesn't have bindable type ) 1513 // base 1514 1515 if (derived.hasIsSummaryElement()) { 1516 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) { 1517 if (base.hasIsSummary()) 1518 throw new Error("Error in profile "+pn+" at "+derived.getPath()+": Base isSummary = "+base.getIsSummaryElement().asStringValue()+", derived isSummary = "+derived.getIsSummaryElement().asStringValue()); 1519 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 1520 } else if (trimDifferential) 1521 derived.setIsSummaryElement(null); 1522 else 1523 derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true); 1524 } 1525 1526 if (derived.hasType()) { 1527 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 1528 if (base.hasType()) { 1529 for (TypeRefComponent ts : derived.getType()) { 1530 boolean ok = false; 1531 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1532 for (TypeRefComponent td : base.getType()) {; 1533 b.append(td.getCode()); 1534 if (td.hasCode() && (td.getCode().equals(ts.getCode()) || td.getCode().equals("Extension") || 1535 td.getCode().equals("Element") || td.getCode().equals("*") || 1536 ((td.getCode().equals("Resource") || (td.getCode().equals("DomainResource")) && pkp.isResource(ts.getCode()))))) 1537 ok = true; 1538 } 1539 if (!ok) 1540 throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+ts.getCode()+" from "+b.toString()); 1541 } 1542 } 1543 base.getType().clear(); 1544 for (TypeRefComponent t : derived.getType()) { 1545 TypeRefComponent tt = t.copy(); 1546// tt.setUserData(DERIVATION_EQUALS, true); 1547 base.getType().add(tt); 1548 } 1549 } 1550 else if (trimDifferential) 1551 derived.getType().clear(); 1552 else 1553 for (TypeRefComponent t : derived.getType()) 1554 t.setUserData(DERIVATION_EQUALS, true); 1555 } 1556 1557 if (derived.hasMapping()) { 1558 // todo: mappings are not cumulative - one replaces another 1559 if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) { 1560 for (ElementDefinitionMappingComponent s : derived.getMapping()) { 1561 boolean found = false; 1562 for (ElementDefinitionMappingComponent d : base.getMapping()) { 1563 found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap())); 1564 } 1565 if (!found) 1566 base.getMapping().add(s); 1567 } 1568 } 1569 else if (trimDifferential) 1570 derived.getMapping().clear(); 1571 else 1572 for (ElementDefinitionMappingComponent t : derived.getMapping()) 1573 t.setUserData(DERIVATION_EQUALS, true); 1574 } 1575 1576 // todo: constraints are cumulative. there is no replacing 1577 for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 1578 s.setUserData(IS_DERIVED, true); 1579 if (!s.hasSource()) 1580 s.setSource(base.getId()); 1581 } 1582 if (derived.hasConstraint()) { 1583 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 1584 ElementDefinitionConstraintComponent inv = s.copy(); 1585 base.getConstraint().add(inv); 1586 } 1587 } 1588 1589 // now, check that we still have a bindable type; if not, delete the binding - see task 8477 1590 if (dest.hasBinding() && !hasBindableType(dest)) 1591 dest.setBinding(null); 1592 1593 // finally, we copy any extensions from source to dest 1594 for (Extension ex : derived.getExtension()) { 1595 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ex.getUrl()); 1596 if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) 1597 ToolingExtensions.removeExtension(dest, ex.getUrl()); 1598 dest.addExtension(ex.copy()); 1599 } 1600 } 1601 } 1602 1603 private boolean hasBindableType(ElementDefinition ed) { 1604 for (TypeRefComponent tr : ed.getType()) { 1605 if (Utilities.existsInList(tr.getCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code")) 1606 return true; 1607 } 1608 return false; 1609 } 1610 1611 1612 private boolean isLargerMax(String derived, String base) { 1613 if ("*".equals(base)) 1614 return false; 1615 if ("*".equals(derived)) 1616 return true; 1617 return Integer.parseInt(derived) > Integer.parseInt(base); 1618 } 1619 1620 1621 private boolean isSubset(ValueSet expBase, ValueSet expDerived) { 1622 return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion()); 1623 } 1624 1625 1626 private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) { 1627 for (ValueSetExpansionContainsComponent cc : contains) { 1628 if (!inExpansion(cc, expansion.getContains())) 1629 return false; 1630 if (!codesInExpansion(cc.getContains(), expansion)) 1631 return false; 1632 } 1633 return true; 1634 } 1635 1636 1637 private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) { 1638 for (ValueSetExpansionContainsComponent cc1 : contains) { 1639 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) 1640 return true; 1641 if (inExpansion(cc, cc1.getContains())) 1642 return true; 1643 } 1644 return false; 1645 } 1646 1647 public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { 1648 for (ElementDefinition edb : base.getSnapshot().getElement()) { 1649 if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { 1650 ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); 1651 if (edm == null) { 1652 ElementDefinition edd = derived.getDifferential().addElement(); 1653 edd.setPath(edb.getPath()); 1654 edd.setMax("0"); 1655 } else if (edb.hasSlicing()) { 1656 closeChildren(base, edb, derived, edm); 1657 } 1658 } 1659 } 1660 sortDifferential(base, derived, derived.getName(), new ArrayList<String>()); 1661 } 1662 1663 private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) { 1664 String path = edb.getPath()+"."; 1665 int baseStart = base.getSnapshot().getElement().indexOf(edb); 1666 int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1); 1667 int diffStart = derived.getDifferential().getElement().indexOf(edm); 1668 int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1); 1669 1670 for (int cBase = baseStart; cBase < baseEnd; cBase++) { 1671 ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); 1672 if (isImmediateChild(edBase, edb)) { 1673 ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd); 1674 if (edMatch == null) { 1675 ElementDefinition edd = derived.getDifferential().addElement(); 1676 edd.setPath(edBase.getPath()); 1677 edd.setMax("0"); 1678 } else { 1679 closeChildren(base, edBase, derived, edMatch); 1680 } 1681 } 1682 } 1683 } 1684 1685 1686 1687 1688 private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) { 1689 String path = ed.getPath()+"."; 1690 while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) 1691 cursor++; 1692 return cursor; 1693 } 1694 1695 1696 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) { 1697 for (ElementDefinition t : list) 1698 if (t.getPath().equals(ed.getPath())) 1699 return t; 1700 return null; 1701 } 1702 1703 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) { 1704 for (int i = start; i < end; i++) { 1705 ElementDefinition t = list.get(i); 1706 if (t.getPath().equals(ed.getPath())) 1707 return t; 1708 } 1709 return null; 1710 } 1711 1712 1713 private boolean isImmediateChild(ElementDefinition ed) { 1714 String p = ed.getPath(); 1715 if (!p.contains(".")) 1716 return false; 1717 p = p.substring(p.indexOf(".")+1); 1718 return !p.contains("."); 1719 } 1720 1721 private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { 1722 String p = candidate.getPath(); 1723 if (!p.contains(".")) 1724 return false; 1725 if (!p.startsWith(base.getPath()+".")) 1726 return false; 1727 p = p.substring(base.getPath().length()+1); 1728 return !p.contains("."); 1729 } 1730 1731 public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException { 1732 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 1733 gen.setTranslator(getTranslator()); 1734 TableModel model = gen.initNormalTable(corePath, false); 1735 1736 boolean deep = false; 1737 String m = ""; 1738 boolean vdeep = false; 1739 if (ed.getSnapshot().getElementFirstRep().getIsModifier()) 1740 m = "modifier_"; 1741 for (ElementDefinition eld : ed.getSnapshot().getElement()) { 1742 deep = deep || eld.getPath().contains("Extension.extension."); 1743 vdeep = vdeep || eld.getPath().contains("Extension.extension.extension."); 1744 } 1745 Row r = gen.new Row(); 1746 model.getRows().add(r); 1747 String en; 1748 if (!full) 1749 en = ed.getName(); 1750 else if (ed.getSnapshot().getElement().get(0).getIsModifier()) 1751 en = "modifierExtension"; 1752 else 1753 en = "extension"; 1754 1755 r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null)); 1756 r.getCells().add(gen.new Cell()); 1757 r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null)); 1758 1759 ElementDefinition ved = null; 1760 if (full || vdeep) { 1761 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 1762 1763 r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1764 List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0)); 1765 for (ElementDefinition child : children) 1766 if (!child.getPath().endsWith(".id")) 1767 genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath, imagePath, true, false, false, false); 1768 } else if (deep) { 1769 List<ElementDefinition> children = new ArrayList<ElementDefinition>(); 1770 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 1771 if (ted.getPath().equals("Extension.extension")) 1772 children.add(ted); 1773 } 1774 1775 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 1776 r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 1777 1778 for (ElementDefinition c : children) { 1779 ved = getValueFor(ed, c); 1780 ElementDefinition ued = getUrlFor(ed, c); 1781 if (ved != null && ued != null) { 1782 Row r1 = gen.new Row(); 1783 r.getSubRows().add(r1); 1784 r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null)); 1785 r1.getCells().add(gen.new Cell()); 1786 r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null)); 1787 genTypes(gen, r1, ved, defFile, ed, corePath, imagePath); 1788 Cell cell = gen.new Cell(); 1789 cell.addMarkdown(c.getDefinition()); 1790 r1.getCells().add(cell); 1791 r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1792 } 1793 } 1794 } else { 1795 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 1796 if (ted.getPath().startsWith("Extension.value")) 1797 ved = ted; 1798 } 1799 1800 genTypes(gen, r, ved, defFile, ed, corePath, imagePath); 1801 1802 r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1803 } 1804 Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null); 1805 Piece cc = gen.new Piece(null, ed.getName()+": ", null); 1806 c.addPiece(gen.new Piece("br")).addPiece(cc); 1807 c.addMarkdown(ed.getDescription()); 1808 1809 if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) { 1810 c.addPiece(gen.new Piece("br")); 1811 BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath()); 1812 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"))); 1813 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null))); 1814 if (ved.getBinding().hasStrength()) { 1815 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null))); 1816 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition()))); 1817 c.getPieces().add(gen.new Piece(null, ")", null)); 1818 } 1819 } 1820 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null)); 1821 r.getCells().add(c); 1822 1823 try { 1824 return gen.generate(model, corePath, 0, outputTracker); 1825 } catch (org.hl7.fhir.exceptions.FHIRException e) { 1826 throw new FHIRException(e.getMessage(), e); 1827 } 1828 } 1829 1830 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 1831 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 1832 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 1833 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url")) 1834 return ed.getSnapshot().getElement().get(i); 1835 i++; 1836 } 1837 return null; 1838 } 1839 1840 private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) { 1841 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 1842 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 1843 if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value")) 1844 return ed.getSnapshot().getElement().get(i); 1845 i++; 1846 } 1847 return null; 1848 } 1849 1850 1851 private static final int AGG_NONE = 0; 1852 private static final int AGG_IND = 1; 1853 private static final int AGG_GR = 2; 1854 private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath) { 1855 Cell c = gen.new Cell(); 1856 r.getCells().add(c); 1857 List<TypeRefComponent> types = e.getType(); 1858 if (!e.hasType()) { 1859 if (e.hasContentReference()) { 1860 return c; 1861 } else { 1862 ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER); 1863 if (d != null && d.hasType()) { 1864 types = new ArrayList<ElementDefinition.TypeRefComponent>(); 1865 for (TypeRefComponent tr : d.getType()) { 1866 TypeRefComponent tt = tr.copy(); 1867 tt.setUserData(DERIVATION_EQUALS, true); 1868 types.add(tt); 1869 } 1870 } else 1871 return c; 1872 } 1873 } 1874 1875 boolean first = true; 1876 1877 TypeRefComponent tl = null; 1878 for (TypeRefComponent t : types) { 1879 if (first) 1880 first = false; 1881 else 1882 c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null))); 1883 tl = t; 1884 if (t.hasTarget()) { 1885 c.getPieces().add(gen.new Piece(corePath+"references.html", t.getCode(), null)); 1886 c.getPieces().add(gen.new Piece(null, "(", null)); 1887 boolean tfirst = true; 1888 for (UriType u : t.getTargetProfile()) { 1889 if (tfirst) 1890 tfirst = false; 1891 else 1892 c.addPiece(gen.new Piece(null, " | ", null)); 1893 if (u.getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 1894 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue()); 1895 if (sd != null) { 1896 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 1897 c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null))); 1898 } else { 1899 String rn = u.getValue().substring(40); 1900 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null))); 1901 } 1902 } else if (Utilities.isAbsoluteUrl(u.getValue())) { 1903 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue()); 1904 if (sd != null) { 1905 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 1906 String ref = pkp.getLinkForProfile(null, sd.getUrl()); 1907 if (ref.contains("|")) 1908 ref = ref.substring(0, ref.indexOf("|")); 1909 c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null))); 1910 } else 1911 c.addPiece(checkForNoChange(t, gen.new Piece(null, u.getValue(), null))); 1912 } else if (t.hasTargetProfile() && u.getValue().startsWith("#")) 1913 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.getValue().substring(1).toLowerCase()+".html", u.getValue(), null))); 1914 } 1915 c.getPieces().add(gen.new Piece(null, ")", null)); 1916 if (t.getAggregation().size() > 0) { 1917 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null)); 1918 boolean firstA = true; 1919 for (Enumeration<AggregationMode> a : t.getAggregation()) { 1920 if (firstA = true) 1921 firstA = false; 1922 else 1923 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null)); 1924 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue()))); 1925 } 1926 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null)); 1927 } 1928 } else if (t.hasProfile() && (!t.getCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type 1929 String ref; 1930 ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue()); 1931 if (ref != null) { 1932 String[] parts = ref.split("\\|"); 1933 if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) { 1934// c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd 1935 c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getCode()))); 1936 } else { 1937// c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode()))); 1938 c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getCode()))); 1939 } 1940 } else 1941 c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath)? corePath: "")+ref, t.getCode(), null))); 1942 } else if (pkp != null && pkp.hasLinkFor(t.getCode())) { 1943 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, t.getCode()), t.getCode(), null))); 1944 } else 1945 c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null))); 1946 } 1947 return c; 1948 } 1949 1950 private boolean isProfiledType(List<CanonicalType> theProfile) { 1951 for (CanonicalType next : theProfile){ 1952 if (StringUtils.defaultString(next.getValueAsString()).contains(":")) { 1953 return true; 1954 } 1955 } 1956 return false; 1957 } 1958 1959 1960 private String codeForAggregation(AggregationMode a) { 1961 switch (a) { 1962 case BUNDLED : return "b"; 1963 case CONTAINED : return "c"; 1964 case REFERENCED: return "r"; 1965 default: return "?"; 1966 } 1967 } 1968 1969 private String hintForAggregation(AggregationMode a) { 1970 if (a != null) 1971 return a.getDefinition(); 1972 else 1973 return null; 1974 } 1975 1976 1977 private String checkPrepend(String corePath, String path) { 1978 if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:"))) 1979 return corePath+path; 1980 else 1981 return path; 1982 } 1983 1984 1985 private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) { 1986 for (ElementDefinition ed : elements) 1987 if (ed.hasSliceName() && ("#"+ed.getSliceName()).equals(contentReference)) 1988 return ed; 1989 return null; 1990 } 1991 1992 private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) { 1993 for (ElementDefinition ed : elements) 1994 if (ed.hasId() && ("#"+ed.getId()).equals(contentReference)) 1995 return ed; 1996 return null; 1997 } 1998 1999 2000 public static String describeExtensionContext(StructureDefinition ext) { 2001 StringBuilder b = new StringBuilder(); 2002 b.append("Use on "); 2003 for (int i = 0; i < ext.getContext().size(); i++) { 2004 StructureDefinitionContextComponent ec = ext.getContext().get(i); 2005 if (i > 0) 2006 b.append(i < ext.getContext().size() - 1 ? ", " : " or "); 2007 b.append(ec.getType().getDisplay()); 2008 b.append(" "); 2009 b.append(ec.getExpression()); 2010 } 2011 return b.toString(); 2012 } 2013 2014 private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) { 2015 IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 2016 StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 2017 if (min.isEmpty() && fallback != null) 2018 min = fallback.getMinElement(); 2019 if (max.isEmpty() && fallback != null) 2020 max = fallback.getMaxElement(); 2021 2022 tracker.used = !max.isEmpty() && !max.getValue().equals("0"); 2023 2024 if (min.isEmpty() && max.isEmpty()) 2025 return null; 2026 else 2027 return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue()); 2028 } 2029 2030 private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) { 2031 IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 2032 StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 2033 if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 2034 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 2035 if (base.hasMinElement()) { 2036 min = base.getMinElement().copy(); 2037 min.setUserData(DERIVATION_EQUALS, true); 2038 } 2039 } 2040 if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 2041 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 2042 if (base.hasMaxElement()) { 2043 max = base.getMaxElement().copy(); 2044 max.setUserData(DERIVATION_EQUALS, true); 2045 } 2046 } 2047 if (min.isEmpty() && fallback != null) 2048 min = fallback.getMinElement(); 2049 if (max.isEmpty() && fallback != null) 2050 max = fallback.getMaxElement(); 2051 2052 if (!max.isEmpty()) 2053 tracker.used = !max.getValue().equals("0"); 2054 2055 Cell cell = gen.new Cell(null, null, null, null, null); 2056 row.getCells().add(cell); 2057 if (!min.isEmpty() || !max.isEmpty()) { 2058 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null))); 2059 cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null))); 2060 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null))); 2061 } 2062 } 2063 2064 2065 private Piece checkForNoChange(Element source, Piece piece) { 2066 if (source.hasUserData(DERIVATION_EQUALS)) { 2067 piece.addStyle("opacity: 0.4"); 2068 } 2069 return piece; 2070 } 2071 2072 private Piece checkForNoChange(Element src1, Element src2, Piece piece) { 2073 if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) { 2074 piece.addStyle("opacity: 0.5"); 2075 } 2076 return piece; 2077 } 2078 2079 public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, boolean logicalModel, boolean allInvariants, Set<String> outputTracker) throws IOException, FHIRException { 2080 assert(diff != snapshot);// check it's ok to get rid of one of these 2081 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 2082 gen.setTranslator(getTranslator()); 2083 TableModel model = gen.initNormalTable(corePath, false); 2084 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 2085 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2086 profiles.add(profile); 2087 if (list.isEmpty()) 2088 throw new FHIRException((diff ? "Differential" : "Snapshot") + " is empty generating hierarchical table for "+profile.getUrl()); 2089 genElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants); 2090 try { 2091 return gen.generate(model, imagePath, 0, outputTracker); 2092 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2093 throw new FHIRException("Error generating table for profile " + profile.getUrl() + ": " + e.getMessage(), e); 2094 } 2095 } 2096 2097 2098 public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException { 2099 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 2100 gen.setTranslator(getTranslator()); 2101 TableModel model = gen.initGridTable(corePath); 2102 List<ElementDefinition> list = profile.getSnapshot().getElement(); 2103 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2104 profiles.add(profile); 2105 genGridElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, true, profileBaseFileName, null, corePath, imagePath, true, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list)); 2106 try { 2107 return gen.generate(model, imagePath, 1, outputTracker); 2108 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2109 throw new FHIRException(e.getMessage(), e); 2110 } 2111 } 2112 2113 2114 private boolean usesMustSupport(List<ElementDefinition> list) { 2115 for (ElementDefinition ed : list) 2116 if (ed.hasMustSupport() && ed.getMustSupport()) 2117 return true; 2118 return false; 2119 } 2120 2121 2122 private void genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants) throws IOException, FHIRException { 2123 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 2124 String s = tail(element.getPath()); 2125 List<ElementDefinition> children = getChildren(all, element); 2126 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 2127// if (!snapshot && isExtension && extensions != null && extensions != isExtension) 2128// return; 2129 2130 if (!onlyInformationIsMapping(all, element)) { 2131 Row row = gen.new Row(); 2132 row.setAnchor(element.getPath()); 2133 row.setColor(getRowColor(element, isConstraintMode)); 2134 if (element.hasSlicing()) 2135 row.setLineColor(1); 2136 else if (element.hasSliceName()) 2137 row.setLineColor(2); 2138 else 2139 row.setLineColor(0); 2140 boolean hasDef = element != null; 2141 boolean ext = false; 2142 if (s.equals("extension")) { 2143 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 2144 row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2145 else 2146 row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2147 ext = true; 2148 } else if (s.equals("modifierExtension")) { 2149 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 2150 row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2151 else 2152 row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2153 } else if (!hasDef || element.getType().size() == 0) 2154 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 2155 else if (hasDef && element.getType().size() > 1) { 2156 if (allAreReference(element.getType())) 2157 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2158 else 2159 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 2160 } else if (hasDef && element.getType().get(0).getCode() != null && element.getType().get(0).getCode().startsWith("@")) 2161 row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE); 2162 else if (hasDef && isPrimitive(element.getType().get(0).getCode())) 2163 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 2164 else if (hasDef && element.getType().get(0).hasTarget()) 2165 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2166 else if (hasDef && isDataType(element.getType().get(0).getCode())) 2167 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2168 else 2169 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 2170 String ref = defPath == null ? null : defPath + element.getId(); 2171 UnusedTracker used = new UnusedTracker(); 2172 used.used = true; 2173 Cell left = gen.new Cell(null, ref, s, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : "")+(hasDef && element.hasSliceName() ? ": " : "")+(!hasDef ? null : gt(element.getDefinitionElement())), null); 2174 row.getCells().add(left); 2175 Cell gc = gen.new Cell(); 2176 row.getCells().add(gc); 2177 if (element != null && element.getIsModifier()) 2178 checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false)); 2179 if (element != null && element.getMustSupport()) 2180 checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false)); 2181 if (element != null && element.getIsSummary()) 2182 checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "Σ", null, null, null, false)); 2183 if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty())) 2184 gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null, null, false); 2185 2186 ExtensionContext extDefn = null; 2187 if (ext) { 2188 if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) { 2189 String eurl = element.getType().get(0).getProfile().get(0).getValue(); 2190 extDefn = locateExtension(StructureDefinition.class, eurl); 2191 if (extDefn == null) { 2192 genCardinality(gen, element, row, hasDef, used, null); 2193 row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null)); 2194 generateDescription(gen, row, element, null, used.used, profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants); 2195 } else { 2196 String name = urltail(eurl); 2197 left.getPieces().get(0).setText(name); 2198 // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename")); 2199 left.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl()); 2200 genCardinality(gen, element, row, hasDef, used, extDefn.getElement()); 2201 ElementDefinition valueDefn = extDefn.getExtensionValueDefinition(); 2202 if (valueDefn != null && !"0".equals(valueDefn.getMax())) 2203 genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath); 2204 else // if it's complex, we just call it nothing 2205 // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile); 2206 row.getCells().add(gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null)); 2207 generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn); 2208 } 2209 } else { 2210 genCardinality(gen, element, row, hasDef, used, null); 2211 if ("0".equals(element.getMax())) 2212 row.getCells().add(gen.new Cell()); 2213 else 2214 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2215 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants); 2216 } 2217 } else { 2218 genCardinality(gen, element, row, hasDef, used, null); 2219 if (hasDef && !"0".equals(element.getMax())) 2220 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2221 else 2222 row.getCells().add(gen.new Cell()); 2223 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants); 2224 } 2225 if (element.hasSlicing()) { 2226 if (standardExtensionSlicing(element)) { 2227 used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing ... element.hasType() && element.getType().get(0).hasProfile(); 2228 showMissing = false; //? 2229 } else { 2230 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 2231 row.getCells().get(2).getPieces().clear(); 2232 for (Cell cell : row.getCells()) 2233 for (Piece p : cell.getPieces()) { 2234 p.addStyle("font-style: italic"); 2235 } 2236 } 2237 } 2238 if (used.used || showMissing) 2239 rows.add(row); 2240 if (!used.used && !element.hasSlicing()) { 2241 for (Cell cell : row.getCells()) 2242 for (Piece p : cell.getPieces()) { 2243 p.setStyle("text-decoration:line-through"); 2244 p.setReference(null); 2245 } 2246 } else{ 2247 for (ElementDefinition child : children) 2248 if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) 2249 genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants); 2250// if (!snapshot && (extensions == null || !extensions)) 2251// for (ElementDefinition child : children) 2252// if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension")) 2253// genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants); 2254 } 2255 } 2256 } 2257 2258 private void genGridElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, String corePath, String imagePath, boolean root, boolean isConstraintMode) throws IOException, FHIRException { 2259 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 2260 String s = tail(element.getPath()); 2261 List<ElementDefinition> children = getChildren(all, element); 2262 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 2263 2264 if (!onlyInformationIsMapping(all, element)) { 2265 Row row = gen.new Row(); 2266 row.setAnchor(element.getPath()); 2267 row.setColor(getRowColor(element, isConstraintMode)); 2268 if (element.hasSlicing()) 2269 row.setLineColor(1); 2270 else if (element.hasSliceName()) 2271 row.setLineColor(2); 2272 else 2273 row.setLineColor(0); 2274 boolean hasDef = element != null; 2275 String ref = defPath == null ? null : defPath + element.getId(); 2276 UnusedTracker used = new UnusedTracker(); 2277 used.used = true; 2278 Cell left = gen.new Cell(); 2279 if (element.getType().size() == 1 && element.getType().get(0).isPrimitive()) 2280 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold")); 2281 else 2282 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement()))); 2283 if (element.hasSliceName()) { 2284 left.getPieces().add(gen.new Piece("br")); 2285 String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length)); 2286 left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null)); 2287 } 2288 row.getCells().add(left); 2289 2290 ExtensionContext extDefn = null; 2291 genCardinality(gen, element, row, hasDef, used, null); 2292 if (hasDef && !"0".equals(element.getMax())) 2293 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2294 else 2295 row.getCells().add(gen.new Cell()); 2296 generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null); 2297/* if (element.hasSlicing()) { 2298 if (standardExtensionSlicing(element)) { 2299 used.used = element.hasType() && element.getType().get(0).hasProfile(); 2300 showMissing = false; 2301 } else { 2302 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 2303 row.getCells().get(2).getPieces().clear(); 2304 for (Cell cell : row.getCells()) 2305 for (Piece p : cell.getPieces()) { 2306 p.addStyle("font-style: italic"); 2307 } 2308 } 2309 }*/ 2310 rows.add(row); 2311 for (ElementDefinition child : children) 2312 if (child.getMustSupport()) 2313 genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode); 2314 } 2315 } 2316 2317 2318 private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value) { 2319 if (value.contains("#")) { 2320 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 2321 if (ext == null) 2322 return null; 2323 String tail = value.substring(value.indexOf("#")+1); 2324 ElementDefinition ed = null; 2325 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 2326 if (tail.equals(ted.getSliceName())) { 2327 ed = ted; 2328 return new ExtensionContext(ext, ed); 2329 } 2330 } 2331 return null; 2332 } else { 2333 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 2334 if (ext == null) 2335 return null; 2336 else 2337 return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0)); 2338 } 2339 } 2340 2341 2342 private boolean extensionIsComplex(String value) { 2343 if (value.contains("#")) { 2344 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 2345 if (ext == null) 2346 return false; 2347 String tail = value.substring(value.indexOf("#")+1); 2348 ElementDefinition ed = null; 2349 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 2350 if (tail.equals(ted.getSliceName())) { 2351 ed = ted; 2352 break; 2353 } 2354 } 2355 if (ed == null) 2356 return false; 2357 int i = ext.getSnapshot().getElement().indexOf(ed); 2358 int j = i+1; 2359 while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath())) 2360 j++; 2361 return j - i > 5; 2362 } else { 2363 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 2364 return ext != null && ext.getSnapshot().getElement().size() > 5; 2365 } 2366 } 2367 2368 2369 private String getRowColor(ElementDefinition element, boolean isConstraintMode) { 2370 switch (element.getUserInt(UD_ERROR_STATUS)) { 2371 case STATUS_HINT: return ROW_COLOR_HINT; 2372 case STATUS_WARNING: return ROW_COLOR_WARNING; 2373 case STATUS_ERROR: return ROW_COLOR_ERROR; 2374 case STATUS_FATAL: return ROW_COLOR_FATAL; 2375 } 2376 if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains(".")) 2377 return null; // ROW_COLOR_NOT_MUST_SUPPORT; 2378 else 2379 return null; 2380 } 2381 2382 2383 private String urltail(String path) { 2384 if (path.contains("#")) 2385 return path.substring(path.lastIndexOf('#')+1); 2386 if (path.contains("/")) 2387 return path.substring(path.lastIndexOf('/')+1); 2388 else 2389 return path; 2390 2391 } 2392 2393 private boolean standardExtensionSlicing(ElementDefinition element) { 2394 String t = tail(element.getPath()); 2395 return (t.equals("extension") || t.equals("modifierExtension")) 2396 && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getPath().equals("url") && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE); 2397 } 2398 2399 private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants) throws IOException, FHIRException { 2400 return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null); 2401 } 2402 2403 private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, ElementDefinition valueDefn) throws IOException, FHIRException { 2404 Cell c = gen.new Cell(); 2405 row.getCells().add(c); 2406 2407 if (used) { 2408 if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) { 2409 if (root) { 2410 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 2411 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 2412 } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 2413 !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) { 2414 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 2415 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 2416 } 2417 } 2418 2419 if (definition.hasContentReference()) { 2420 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference()); 2421 if (ed == null) 2422 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null)); 2423 else 2424 c.getPieces().add(gen.new Piece("#"+ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null)); 2425 } 2426 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 2427 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 2428 } else { 2429 if (definition != null && definition.hasShort()) { 2430 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2431 c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null))); 2432 } else if (fallback != null && fallback.hasShort()) { 2433 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2434 c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null))); 2435 } 2436 if (url != null) { 2437 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2438 String fullUrl = url.startsWith("#") ? baseURL+url : url; 2439 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 2440 String ref = null; 2441 String ref2 = null; 2442 String fixedUrl = null; 2443 if (ed != null) { 2444 String p = ed.getUserString("path"); 2445 if (p != null) { 2446 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 2447 } 2448 fixedUrl = getFixedUrl(ed); 2449 if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension? 2450 if (fixedUrl.equals(url)) 2451 fixedUrl = null; 2452 else { 2453 StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl); 2454 if (ed2 != null) { 2455 String p2 = ed2.getUserString("path"); 2456 if (p2 != null) { 2457 ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2); 2458 } 2459 } 2460 } 2461 } 2462 } 2463 if (fixedUrl == null) { 2464 c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold")); 2465 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 2466 } else { 2467 // reference to a profile take on the extension show the base URL 2468 c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold")); 2469 c.getPieces().add(gen.new Piece(ref2, fixedUrl, null)); 2470 c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold")); 2471 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 2472 2473 } 2474 } 2475 2476 if (definition.hasSlicing()) { 2477 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2478 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold")); 2479 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 2480 } 2481 if (definition != null) { 2482 ElementDefinitionBindingComponent binding = null; 2483 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 2484 binding = valueDefn.getBinding(); 2485 else if (definition.hasBinding()) 2486 binding = definition.getBinding(); 2487 if (binding!=null && !binding.isEmpty()) { 2488 if (!c.getPieces().isEmpty()) 2489 c.addPiece(gen.new Piece("br")); 2490 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 2491 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"))); 2492 c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null))); 2493 if (binding.hasStrength()) { 2494 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 2495 c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition()))); 2496 c.getPieces().add(gen.new Piece(null, ")", null)); 2497 } 2498 } 2499 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 2500 if (!inv.hasSource() || allInvariants) { 2501 if (!c.getPieces().isEmpty()) 2502 c.addPiece(gen.new Piece("br")); 2503 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 2504 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null))); 2505 } 2506 } 2507 if ((definition.hasBase() && definition.getBase().getMax().equals("*")) || (definition.hasMax() && definition.getMax().equals("*"))) { 2508 if (c.getPieces().size() > 0) 2509 c.addPiece(gen.new Piece("br")); 2510 if (definition.hasOrderMeaning()) { 2511 c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null)); 2512 } else { 2513 // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null)); 2514 } 2515 } 2516 2517 if (definition.hasFixed()) { 2518 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2519 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold"))); 2520 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen"))); 2521 if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) { 2522 Piece p = describeCoded(gen, definition.getFixed()); 2523 if (p != null) 2524 c.getPieces().add(p); 2525 } 2526 } else if (definition.hasPattern()) { 2527 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2528 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold"))); 2529 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 2530 } else if (definition.hasExample()) { 2531 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 2532 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2533 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold"))); 2534 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 2535 } 2536 } 2537 if (definition.hasMaxLength() && definition.getMaxLength()!=0) { 2538 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2539 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 2540 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 2541 } 2542 if (profile != null) { 2543 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 2544 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 2545 ElementDefinitionMappingComponent map = null; 2546 for (ElementDefinitionMappingComponent m : definition.getMapping()) 2547 if (m.getIdentity().equals(md.getIdentity())) 2548 map = m; 2549 if (map != null) { 2550 for (int i = 0; i<definition.getMapping().size(); i++){ 2551 c.addPiece(gen.new Piece("br")); 2552 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null)); 2553 } 2554 } 2555 } 2556 } 2557 } 2558 } 2559 } 2560 } 2561 return c; 2562 } 2563 2564 private String getFixedUrl(StructureDefinition sd) { 2565 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2566 if (ed.getPath().equals("Extension.url")) { 2567 if (ed.hasFixed() && ed.getFixed() instanceof UriType) 2568 return ed.getFixed().primitiveValue(); 2569 } 2570 } 2571 return null; 2572 } 2573 2574 2575 private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) { 2576 if (fixed instanceof Coding) { 2577 Coding c = (Coding) fixed; 2578 ValidationResult vr = context.validateCode(c.getSystem(), c.getCode(), c.getDisplay()); 2579 if (vr.getDisplay() != null) 2580 return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); 2581 } else if (fixed instanceof CodeableConcept) { 2582 CodeableConcept cc = (CodeableConcept) fixed; 2583 for (Coding c : cc.getCoding()) { 2584 ValidationResult vr = context.validateCode(c.getSystem(), c.getCode(), c.getDisplay()); 2585 if (vr.getDisplay() != null) 2586 return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); 2587 } 2588 } 2589 return null; 2590 } 2591 2592 2593 private boolean hasDescription(Type fixed) { 2594 if (fixed instanceof Coding) { 2595 return ((Coding) fixed).hasDisplay(); 2596 } else if (fixed instanceof CodeableConcept) { 2597 CodeableConcept cc = (CodeableConcept) fixed; 2598 if (cc.hasText()) 2599 return true; 2600 for (Coding c : cc.getCoding()) 2601 if (c.hasDisplay()) 2602 return true; 2603 } // (fixed instanceof CodeType) || (fixed instanceof Quantity); 2604 return false; 2605 } 2606 2607 2608 private boolean isCoded(Type fixed) { 2609 return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity); 2610 } 2611 2612 2613 private Cell generateGridDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, ElementDefinition valueDefn) throws IOException, FHIRException { 2614 Cell c = gen.new Cell(); 2615 row.getCells().add(c); 2616 2617 if (used) { 2618 if (definition.hasContentReference()) { 2619 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference()); 2620 if (ed == null) 2621 c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null)); 2622 else 2623 c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null)); 2624 } 2625 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 2626 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 2627 } else { 2628 if (url != null) { 2629 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2630 String fullUrl = url.startsWith("#") ? baseURL+url : url; 2631 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 2632 String ref = null; 2633 if (ed != null) { 2634 String p = ed.getUserString("path"); 2635 if (p != null) { 2636 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 2637 } 2638 } 2639 c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold")); 2640 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 2641 } 2642 2643 if (definition.hasSlicing()) { 2644 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2645 c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); 2646 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 2647 } 2648 if (definition != null) { 2649 ElementDefinitionBindingComponent binding = null; 2650 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 2651 binding = valueDefn.getBinding(); 2652 else if (definition.hasBinding()) 2653 binding = definition.getBinding(); 2654 if (binding!=null && !binding.isEmpty()) { 2655 if (!c.getPieces().isEmpty()) 2656 c.addPiece(gen.new Piece("br")); 2657 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 2658 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold"))); 2659 c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null))); 2660 if (binding.hasStrength()) { 2661 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 2662 c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), binding.getStrength().toCode(), binding.getStrength().getDefinition()))); c.getPieces().add(gen.new Piece(null, ")", null)); 2663 } 2664 } 2665 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 2666 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2667 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 2668 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); 2669 } 2670 if (definition.hasFixed()) { 2671 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2672 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); 2673 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen"))); 2674 } else if (definition.hasPattern()) { 2675 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2676 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); 2677 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 2678 } else if (definition.hasExample()) { 2679 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 2680 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2681 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold"))); 2682 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 2683 } 2684 } 2685 if (definition.hasMaxLength() && definition.getMaxLength()!=0) { 2686 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2687 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 2688 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 2689 } 2690 if (profile != null) { 2691 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 2692 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 2693 ElementDefinitionMappingComponent map = null; 2694 for (ElementDefinitionMappingComponent m : definition.getMapping()) 2695 if (m.getIdentity().equals(md.getIdentity())) 2696 map = m; 2697 if (map != null) { 2698 for (int i = 0; i<definition.getMapping().size(); i++){ 2699 c.addPiece(gen.new Piece("br")); 2700 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null)); 2701 } 2702 } 2703 } 2704 } 2705 } 2706 if (definition.hasDefinition()) { 2707 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2708 c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold")); 2709 c.addPiece(gen.new Piece("br")); 2710 c.addMarkdown(definition.getDefinition()); 2711// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 2712 } 2713 if (definition.getComment()!=null) { 2714 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2715 c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold")); 2716 c.addPiece(gen.new Piece("br")); 2717 c.addMarkdown(definition.getComment()); 2718// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 2719 } 2720 } 2721 } 2722 } 2723 return c; 2724 } 2725 2726 2727 2728 private String buildJson(Type value) throws IOException { 2729 if (value instanceof PrimitiveType) 2730 return ((PrimitiveType) value).asStringValue(); 2731 2732 IParser json = context.newJsonParser(); 2733 return json.composeString(value, null); 2734 } 2735 2736 2737 public String describeSlice(ElementDefinitionSlicingComponent slicing) { 2738 return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator())); 2739 } 2740 2741 private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) { 2742 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 2743 for (ElementDefinitionSlicingDiscriminatorComponent id : list) 2744 c.append(id.getType().toCode()+":"+id.getPath()); 2745 return c.toString(); 2746 } 2747 2748 2749 private String describe(SlicingRules rules) { 2750 if (rules == null) 2751 return translate("sd.table", "Unspecified"); 2752 switch (rules) { 2753 case CLOSED : return translate("sd.table", "Closed"); 2754 case OPEN : return translate("sd.table", "Open"); 2755 case OPENATEND : return translate("sd.table", "Open At End"); 2756 default: 2757 return "??"; 2758 } 2759 } 2760 2761 private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) { 2762 return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) && 2763 getChildren(list, e).isEmpty(); 2764 } 2765 2766 private boolean onlyInformationIsMapping(ElementDefinition d) { 2767 return !d.hasShort() && !d.hasDefinition() && 2768 !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() && 2769 !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() && 2770 !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() && 2771 !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() && 2772 !d.hasBinding(); 2773 } 2774 2775 private boolean allAreReference(List<TypeRefComponent> types) { 2776 for (TypeRefComponent t : types) { 2777 if (!t.hasTarget()) 2778 return false; 2779 } 2780 return true; 2781 } 2782 2783 private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) { 2784 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 2785 int i = all.indexOf(element)+1; 2786 while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) { 2787 if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains(".")) 2788 result.add(all.get(i)); 2789 i++; 2790 } 2791 return result; 2792 } 2793 2794 private String tail(String path) { 2795 if (path.contains(".")) 2796 return path.substring(path.lastIndexOf('.')+1); 2797 else 2798 return path; 2799 } 2800 2801 private boolean isDataType(String value) { 2802 StructureDefinition sd = context.fetchTypeDefinition(value); 2803 return sd != null && sd.getKind() == StructureDefinitionKind.COMPLEXTYPE; 2804 } 2805 2806 2807 public boolean isPrimitive(String value) { 2808 StructureDefinition sd = context.fetchTypeDefinition(value); 2809 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 2810 } 2811 2812// private static String listStructures(StructureDefinition p) { 2813// StringBuilder b = new StringBuilder(); 2814// boolean first = true; 2815// for (ProfileStructureComponent s : p.getStructure()) { 2816// if (first) 2817// first = false; 2818// else 2819// b.append(", "); 2820// if (pkp != null && pkp.hasLinkFor(s.getType())) 2821// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 2822// else 2823// b.append(s.getType()); 2824// } 2825// return b.toString(); 2826// } 2827 2828 2829 public StructureDefinition getProfile(StructureDefinition source, String url) { 2830 StructureDefinition profile = null; 2831 String code = null; 2832 if (url.startsWith("#")) { 2833 profile = source; 2834 code = url.substring(1); 2835 } else if (context != null) { 2836 String[] parts = url.split("\\#"); 2837 profile = context.fetchResource(StructureDefinition.class, parts[0]); 2838 code = parts.length == 1 ? null : parts[1]; 2839 } 2840 if (profile == null) 2841 return null; 2842 if (code == null) 2843 return profile; 2844 for (Resource r : profile.getContained()) { 2845 if (r instanceof StructureDefinition && r.getId().equals(code)) 2846 return (StructureDefinition) r; 2847 } 2848 return null; 2849 } 2850 2851 2852 2853 public static class ElementDefinitionHolder { 2854 private String name; 2855 private ElementDefinition self; 2856 private int baseIndex = 0; 2857 private List<ElementDefinitionHolder> children; 2858 private boolean placeHolder = false; 2859 2860 public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) { 2861 super(); 2862 this.self = self; 2863 this.name = self.getPath(); 2864 this.placeHolder = isPlaceholder; 2865 children = new ArrayList<ElementDefinitionHolder>(); 2866 } 2867 2868 public ElementDefinitionHolder(ElementDefinition self) { 2869 this(self, false); 2870 } 2871 2872 public ElementDefinition getSelf() { 2873 return self; 2874 } 2875 2876 public List<ElementDefinitionHolder> getChildren() { 2877 return children; 2878 } 2879 2880 public int getBaseIndex() { 2881 return baseIndex; 2882 } 2883 2884 public void setBaseIndex(int baseIndex) { 2885 this.baseIndex = baseIndex; 2886 } 2887 2888 public boolean isPlaceHolder() { 2889 return this.placeHolder; 2890 } 2891 2892 @Override 2893 public String toString() { 2894 if (self.hasSliceName()) 2895 return self.getPath()+"("+self.getSliceName()+")"; 2896 else 2897 return self.getPath(); 2898 } 2899 } 2900 2901 public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 2902 2903 private boolean inExtension; 2904 private List<ElementDefinition> snapshot; 2905 private int prefixLength; 2906 private String base; 2907 private String name; 2908 private Set<String> errors = new HashSet<String>(); 2909 2910 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) { 2911 this.inExtension = inExtension; 2912 this.snapshot = snapshot; 2913 this.prefixLength = prefixLength; 2914 this.base = base; 2915 this.name = name; 2916 } 2917 2918 @Override 2919 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 2920 if (o1.getBaseIndex() == 0) 2921 o1.setBaseIndex(find(o1.getSelf().getPath())); 2922 if (o2.getBaseIndex() == 0) 2923 o2.setBaseIndex(find(o2.getSelf().getPath())); 2924 return o1.getBaseIndex() - o2.getBaseIndex(); 2925 } 2926 2927 private int find(String path) { 2928 int lc = 0; 2929 String actual = base+path.substring(prefixLength); 2930 for (int i = 0; i < snapshot.size(); i++) { 2931 String p = snapshot.get(i).getPath(); 2932 if (p.equals(actual)) { 2933 return i; 2934 } 2935 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) { 2936 return i; 2937 } 2938 if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) { 2939 String ref = snapshot.get(i).getContentReference(); 2940 if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) { 2941 actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 2942 path = actual; 2943 } else { 2944 // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that 2945 actual = base+(path.substring(0, path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 2946 path = actual; 2947 } 2948 2949 i = 0; 2950 lc++; 2951 if (lc > 5) 2952 throw new Error("Error sorting: find() loop count > 5 - check paths are valid"); 2953 } 2954 } 2955 if (prefixLength == 0) 2956 errors.add("Differential contains path "+path+" which is not found in the base"); 2957 else 2958 errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base"); 2959 return 0; 2960 } 2961 2962 public void checkForErrors(List<String> errorList) { 2963 if (errors.size() > 0) { 2964// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2965// for (String s : errors) 2966// b.append("StructureDefinition "+name+": "+s); 2967// throw new DefinitionException(b.toString()); 2968 for (String s : errors) 2969 if (s.startsWith("!")) 2970 errorList.add("!StructureDefinition "+name+": "+s.substring(1)); 2971 else 2972 errorList.add("StructureDefinition "+name+": "+s); 2973 } 2974 } 2975 } 2976 2977 2978 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) throws FHIRException { 2979 2980 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 2981 // first, we move the differential elements into a tree 2982 if (diffList.isEmpty()) 2983 return; 2984 2985 ElementDefinitionHolder edh = null; 2986 int i = 0; 2987 if (diffList.get(0).getPath().contains(".")) { 2988 String newPath = diffList.get(0).getPath().split("\\.")[0]; 2989 ElementDefinition e = new ElementDefinition(new StringType(newPath)); 2990 edh = new ElementDefinitionHolder(e, true); 2991 } else { 2992 edh = new ElementDefinitionHolder(diffList.get(0)); 2993 i = 1; 2994 } 2995 2996 boolean hasSlicing = false; 2997 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 2998 for(ElementDefinition elt : diffList) { 2999 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 3000 hasSlicing = true; 3001 break; 3002 } 3003 paths.add(elt.getPath()); 3004 } 3005 if(!hasSlicing) { 3006 // if Differential does not have slicing then safe to pre-sort the list 3007 // so elements and subcomponents are together 3008 Collections.sort(diffList, new ElementNameCompare()); 3009 } 3010 3011 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 3012 3013 // now, we sort the siblings throughout the tree 3014 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name); 3015 sortElements(edh, cmp, errors); 3016 3017 // now, we serialise them back to a list 3018 diffList.clear(); 3019 writeElements(edh, diffList); 3020 } 3021 3022 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 3023 String path = edh.getSelf().getPath(); 3024 final String prefix = path + "."; 3025 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 3026 if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) { 3027 String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0]; 3028 ElementDefinition e = new ElementDefinition(new StringType(newPath)); 3029 ElementDefinitionHolder child = new ElementDefinitionHolder(e, true); 3030 edh.getChildren().add(child); 3031 i = processElementsIntoTree(child, i, list); 3032 3033 } else { 3034 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 3035 edh.getChildren().add(child); 3036 i = processElementsIntoTree(child, i+1, list); 3037 } 3038 } 3039 return i; 3040 } 3041 3042 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException { 3043 if (edh.getChildren().size() == 1) 3044 // special case - sort needsto allocate base numbers, but there'll be no sort if there's only 1 child. So in that case, we just go ahead and allocated base number directly 3045 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath()); 3046 else 3047 Collections.sort(edh.getChildren(), cmp); 3048 cmp.checkForErrors(errors); 3049 3050 for (ElementDefinitionHolder child : edh.getChildren()) { 3051 if (child.getChildren().size() > 0) { 3052 // what we have to check for here is running off the base profile into a data type profile 3053 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 3054 ElementDefinitionComparer ccmp; 3055 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getCode()) || ed.getType().get(0).getCode().equals(ed.getPath())) { 3056 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name); 3057 } else if (ed.getType().get(0).getCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) { 3058 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 3059 if (profile==null) 3060 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 3061 else 3062 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name); 3063 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getCode().equals("*")) { 3064 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getCode())); 3065 if (profile==null) 3066 throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getCode()) + " in element " + ed.getPath()); 3067 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name); 3068 } else if (child.getSelf().getType().size() == 1) { 3069 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getCode())); 3070 if (profile==null) 3071 throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getCode()) + " in element " + ed.getPath()); 3072 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name); 3073 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 3074 String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3075 String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3076 String p = childLastNode.substring(edLastNode.length()-3); 3077 if (isPrimitive(Utilities.uncapitalize(p))) 3078 p = Utilities.uncapitalize(p); 3079 StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p)); 3080 if (sd == null) 3081 throw new Error("Unable to find profile "+p); 3082 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name); 3083 } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getCode().equals("Reference")) { 3084 for (TypeRefComponent t: child.getSelf().getType()) { 3085 if (!t.getCode().equals("Reference")) { 3086 throw new Error("Can't have children on an element with a polymorphic type - you must slice and constrain the types first (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3087 } 3088 } 3089 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getCode())); 3090 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name); 3091 } else if (!child.getSelf().hasType() && ed.getType().get(0).getCode().equals("Reference")) { 3092 for (TypeRefComponent t: ed.getType()) { 3093 if (!t.getCode().equals("Reference")) { 3094 throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3095 } 3096 } 3097 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getCode())); 3098 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name); 3099 } else { 3100 throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3101 } 3102 if (ccmp != null) 3103 sortElements(child, ccmp, errors); 3104 } 3105 } 3106 } 3107 3108 private static String sdNs(String type) { 3109 return sdNs(type, null); 3110 } 3111 3112 public static String sdNs(String type, String overrideVersionNs) { 3113 if (Utilities.isAbsoluteUrl(type)) 3114 return type; 3115 else if (overrideVersionNs != null) 3116 return Utilities.pathURL(overrideVersionNs, type); 3117 else 3118 return "http://hl7.org/fhir/StructureDefinition/"+type; 3119 } 3120 3121 3122 private boolean isAbstract(String code) { 3123 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource"); 3124 } 3125 3126 3127 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 3128 if (!edh.isPlaceHolder()) 3129 list.add(edh.getSelf()); 3130 for (ElementDefinitionHolder child : edh.getChildren()) { 3131 writeElements(child, list); 3132 } 3133 } 3134 3135 /** 3136 * First compare element by path then by name if same 3137 */ 3138 private static class ElementNameCompare implements Comparator<ElementDefinition> { 3139 3140 @Override 3141 public int compare(ElementDefinition o1, ElementDefinition o2) { 3142 String path1 = normalizePath(o1); 3143 String path2 = normalizePath(o2); 3144 int cmp = path1.compareTo(path2); 3145 if (cmp == 0) { 3146 String name1 = o1.hasSliceName() ? o1.getSliceName() : ""; 3147 String name2 = o2.hasSliceName() ? o2.getSliceName() : ""; 3148 cmp = name1.compareTo(name2); 3149 } 3150 return cmp; 3151 } 3152 3153 private static String normalizePath(ElementDefinition e) { 3154 if (!e.hasPath()) return ""; 3155 String path = e.getPath(); 3156 // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc. 3157 // so strip off the [x] suffix when comparing the path names. 3158 if (path.endsWith("[x]")) { 3159 path = path.substring(0, path.length()-3); 3160 } 3161 return path; 3162 } 3163 3164 } 3165 3166 3167 3168 private class Slicer extends ElementDefinitionSlicingComponent { 3169 String criteria = ""; 3170 String name = ""; 3171 boolean check; 3172 public Slicer(boolean cantCheck) { 3173 super(); 3174 this.check = cantCheck; 3175 } 3176 } 3177 3178 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) { 3179 // given a child in a structure, it's sliced. figure out the slicing xpath 3180 if (child.getPath().endsWith(".extension")) { 3181 ElementDefinition ued = getUrlFor(structure, child); 3182 if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile()))) 3183 return new Slicer(false); 3184 else { 3185 Slicer s = new Slicer(true); 3186 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue(); 3187 s.name = " with URL = '"+url+"'"; 3188 s.criteria = "[@url = '"+url+"']"; 3189 return s; 3190 } 3191 } else 3192 return new Slicer(false); 3193 } 3194 3195 3196 3197 3198 private ElementDefinition getByPath(StructureDefinition base, String path) { 3199 for (ElementDefinition ed : base.getSnapshot().getElement()) { 3200 if (ed.getPath().equals(path)) 3201 return ed; 3202 if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length()-3 && ed.getPath().substring(0, ed.getPath().length()-3).equals(path.substring(0, ed.getPath().length()-3))) 3203 return ed; 3204 } 3205 return null; 3206 } 3207 3208 3209 public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException { 3210 if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { 3211 if (!sd.hasDifferential()) 3212 sd.setDifferential(new StructureDefinitionDifferentialComponent()); 3213 generateIds(sd.getDifferential().getElement(), sd.getUrl()); 3214 } 3215 if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { 3216 if (!sd.hasSnapshot()) 3217 sd.setSnapshot(new StructureDefinitionSnapshotComponent()); 3218 generateIds(sd.getSnapshot().getElement(), sd.getUrl()); 3219 } 3220 } 3221 3222 3223 private boolean hasMissingIds(List<ElementDefinition> list) { 3224 for (ElementDefinition ed : list) { 3225 if (!ed.hasId()) 3226 return true; 3227 } 3228 return false; 3229 } 3230 3231 public class SliceList { 3232 3233 private Map<String, String> slices = new HashMap<>(); 3234 3235 public void seeElement(ElementDefinition ed) { 3236 Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator(); 3237 while (iter.hasNext()) { 3238 Map.Entry<String,String> entry = iter.next(); 3239 if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath())) 3240 iter.remove(); 3241 } 3242 3243 if (ed.hasSliceName()) 3244 slices.put(ed.getPath(), ed.getSliceName()); 3245 } 3246 3247 public String[] analyse(List<String> paths) { 3248 String s = paths.get(0); 3249 String[] res = new String[paths.size()]; 3250 res[0] = null; 3251 for (int i = 1; i < paths.size(); i++) { 3252 s = s + "."+paths.get(i); 3253 if (slices.containsKey(s)) 3254 res[i] = slices.get(s); 3255 else 3256 res[i] = null; 3257 } 3258 return res; 3259 } 3260 3261 } 3262 3263 private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException { 3264 if (list.isEmpty()) 3265 return; 3266 3267 Map<String, String> idMap = new HashMap<String, String>(); 3268 Map<String, String> idList = new HashMap<String, String>(); 3269 3270 SliceList sliceInfo = new SliceList(); 3271 // first pass, update the element ids 3272 for (ElementDefinition ed : list) { 3273 List<String> paths = new ArrayList<String>(); 3274 if (!ed.hasPath()) 3275 throw new DefinitionException("No path on element Definition "+Integer.toString(list.indexOf(ed))+" in "+name); 3276 sliceInfo.seeElement(ed); 3277 String[] pl = ed.getPath().split("\\."); 3278 for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus 3279 paths.add(pl[i]); 3280 String slices[] = sliceInfo.analyse(paths); 3281 3282 StringBuilder b = new StringBuilder(); 3283 b.append(paths.get(0)); 3284 for (int i = 1; i < paths.size(); i++) { 3285 b.append("."); 3286 String s = paths.get(i); 3287 String p = slices[i]; 3288 b.append(s); 3289 if (p != null) { 3290 b.append(":"); 3291 b.append(p); 3292 } 3293 } 3294 String bs = b.toString(); 3295 idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs); 3296 ed.setId(bs); 3297 if (idList.containsKey(bs)) { 3298 if (exception) 3299 throw new DefinitionException("Same id '"+bs+"'on multiple elements "+idList.get(bs)+"/"+ed.getPath()+" in "+name); 3300 else 3301 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR)); 3302 } 3303 idList.put(bs, ed.getPath()); 3304 if (ed.hasContentReference()) { 3305 String s = ed.getContentReference().substring(1); 3306 if (idMap.containsKey(s)) 3307 ed.setContentReference("#"+idMap.get(s)); 3308 3309 } 3310 } 3311 // second path - fix up any broken path based id references 3312 3313 } 3314 3315 3316// private String describeExtension(ElementDefinition ed) { 3317// if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) 3318// return ""; 3319// return "$"+urlTail(ed.getTypeFirstRep().getProfile()); 3320// } 3321// 3322 3323 private String urlTail(String profile) { 3324 return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile; 3325 } 3326 3327 3328 private String checkName(String name) { 3329// if (name.contains(".")) 3330//// throw new Exception("Illegal name "+name+": no '.'"); 3331// if (name.contains(" ")) 3332// throw new Exception("Illegal name "+name+": no spaces"); 3333 StringBuilder b = new StringBuilder(); 3334 for (char c : name.toCharArray()) { 3335 if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']')) 3336 b.append(c); 3337 } 3338 return b.toString().toLowerCase(); 3339 } 3340 3341 3342 private int charCount(String path, char t) { 3343 int res = 0; 3344 for (char ch : path.toCharArray()) { 3345 if (ch == t) 3346 res++; 3347 } 3348 return res; 3349 } 3350 3351// 3352//private void generateForChild(TextStreamWriter txt, 3353// StructureDefinition structure, ElementDefinition child) { 3354// // TODO Auto-generated method stub 3355// 3356//} 3357 3358 private interface ExampleValueAccessor { 3359 Type getExampleValue(ElementDefinition ed); 3360 String getId(); 3361 } 3362 3363 private class BaseExampleValueAccessor implements ExampleValueAccessor { 3364 @Override 3365 public Type getExampleValue(ElementDefinition ed) { 3366 if (ed.hasFixed()) 3367 return ed.getFixed(); 3368 if (ed.hasExample()) 3369 return ed.getExample().get(0).getValue(); 3370 else 3371 return null; 3372 } 3373 3374 @Override 3375 public String getId() { 3376 return "-genexample"; 3377 } 3378 } 3379 3380 private class ExtendedExampleValueAccessor implements ExampleValueAccessor { 3381 private String index; 3382 3383 public ExtendedExampleValueAccessor(String index) { 3384 this.index = index; 3385 } 3386 @Override 3387 public Type getExampleValue(ElementDefinition ed) { 3388 if (ed.hasFixed()) 3389 return ed.getFixed(); 3390 for (Extension ex : ed.getExtension()) { 3391 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 3392 Type value = ToolingExtensions.getExtension(ex, "exValue").getValue(); 3393 if (index.equals(ndx) && value != null) 3394 return value; 3395 } 3396 return null; 3397 } 3398 @Override 3399 public String getId() { 3400 return "-genexample-"+index; 3401 } 3402 } 3403 3404 public List<org.hl7.fhir.r4.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException { 3405 List<org.hl7.fhir.r4.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>(); 3406 if (sd.hasSnapshot()) { 3407 if (evenWhenNoExamples || hasAnyExampleValues(sd)) 3408 examples.add(generateExample(sd, new BaseExampleValueAccessor())); 3409 for (int i = 1; i <= 50; i++) { 3410 if (hasAnyExampleValues(sd, Integer.toString(i))) 3411 examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i)))); 3412 } 3413 } 3414 return examples; 3415 } 3416 3417 private org.hl7.fhir.r4.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException { 3418 ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 3419 org.hl7.fhir.r4.elementmodel.Element r = new org.hl7.fhir.r4.elementmodel.Element(ed.getPath(), new Property(context, ed, profile)); 3420 List<ElementDefinition> children = getChildMap(profile, ed); 3421 for (ElementDefinition child : children) { 3422 if (child.getPath().endsWith(".id")) { 3423 org.hl7.fhir.r4.elementmodel.Element id = new org.hl7.fhir.r4.elementmodel.Element("id", new Property(context, child, profile)); 3424 id.setValue(profile.getId()+accessor.getId()); 3425 r.getChildren().add(id); 3426 } else { 3427 org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor); 3428 if (e != null) 3429 r.getChildren().add(e); 3430 } 3431 } 3432 return r; 3433 } 3434 3435 private org.hl7.fhir.r4.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException { 3436 Type v = accessor.getExampleValue(ed); 3437 if (v != null) { 3438 return new ObjectConverter(context).convert(new Property(context, ed, profile), v); 3439 } else { 3440 org.hl7.fhir.r4.elementmodel.Element res = new org.hl7.fhir.r4.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile)); 3441 boolean hasValue = false; 3442 List<ElementDefinition> children = getChildMap(profile, ed); 3443 for (ElementDefinition child : children) { 3444 if (!child.hasContentReference()) { 3445 org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor); 3446 if (e != null) { 3447 hasValue = true; 3448 res.getChildren().add(e); 3449 } 3450 } 3451 } 3452 if (hasValue) 3453 return res; 3454 else 3455 return null; 3456 } 3457 } 3458 3459 private boolean hasAnyExampleValues(StructureDefinition sd, String index) { 3460 for (ElementDefinition ed : sd.getSnapshot().getElement()) 3461 for (Extension ex : ed.getExtension()) { 3462 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 3463 Extension exv = ToolingExtensions.getExtension(ex, "exValue"); 3464 if (exv != null) { 3465 Type value = exv.getValue(); 3466 if (index.equals(ndx) && value != null) 3467 return true; 3468 } 3469 } 3470 return false; 3471 } 3472 3473 3474 private boolean hasAnyExampleValues(StructureDefinition sd) { 3475 for (ElementDefinition ed : sd.getSnapshot().getElement()) 3476 if (ed.hasExample()) 3477 return true; 3478 return false; 3479 } 3480 3481 3482 public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException { 3483 sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy()); 3484 3485 if (sd.hasBaseDefinition()) { 3486 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 3487 if (base == null) 3488 throw new FHIRException("Unable to find base definition for logical model: "+sd.getBaseDefinition()+" from "+sd.getUrl()); 3489 copyElements(sd, base.getSnapshot().getElement()); 3490 } 3491 copyElements(sd, sd.getDifferential().getElement()); 3492 } 3493 3494 3495 private void copyElements(StructureDefinition sd, List<ElementDefinition> list) { 3496 for (ElementDefinition ed : list) { 3497 if (ed.getPath().contains(".")) { 3498 ElementDefinition n = ed.copy(); 3499 n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1)); 3500 sd.getSnapshot().addElement(n); 3501 } 3502 } 3503 } 3504 3505 3506 public void cleanUpDifferential(StructureDefinition sd) { 3507 if (sd.getDifferential().getElement().size() > 1) 3508 cleanUpDifferential(sd, 1); 3509 } 3510 3511 private void cleanUpDifferential(StructureDefinition sd, int start) { 3512 int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.'); 3513 int c = start; 3514 int len = sd.getDifferential().getElement().size(); 3515 HashSet<String> paths = new HashSet<String>(); 3516 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) { 3517 ElementDefinition ed = sd.getDifferential().getElement().get(c); 3518 if (!paths.contains(ed.getPath())) { 3519 paths.add(ed.getPath()); 3520 int ic = c+1; 3521 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 3522 ic++; 3523 ElementDefinition slicer = null; 3524 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 3525 slices.add(ed); 3526 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) { 3527 ElementDefinition edi = sd.getDifferential().getElement().get(ic); 3528 if (ed.getPath().equals(edi.getPath())) { 3529 if (slicer == null) { 3530 slicer = new ElementDefinition(); 3531 slicer.setPath(edi.getPath()); 3532 slicer.getSlicing().setRules(SlicingRules.OPEN); 3533 sd.getDifferential().getElement().add(c, slicer); 3534 c++; 3535 ic++; 3536 } 3537 slices.add(edi); 3538 } 3539 ic++; 3540 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 3541 ic++; 3542 } 3543 // now we're at the end, we're going to figure out the slicing discriminator 3544 if (slicer != null) 3545 determineSlicing(slicer, slices); 3546 } 3547 c++; 3548 if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) { 3549 cleanUpDifferential(sd, c); 3550 c++; 3551 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 3552 c++; 3553 } 3554 } 3555 } 3556 3557 3558 private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) { 3559 // first, name them 3560 int i = 0; 3561 for (ElementDefinition ed : slices) { 3562 if (ed.hasUserData("slice-name")) { 3563 ed.setSliceName(ed.getUserString("slice-name")); 3564 } else { 3565 i++; 3566 ed.setSliceName("slice-"+Integer.toString(i)); 3567 } 3568 } 3569 // now, the hard bit, how are they differentiated? 3570 // right now, we hard code this... 3571 if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension")) 3572 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 3573 else if (slicer.getPath().equals("DiagnosticReport.result")) 3574 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code"); 3575 else if (slicer.getPath().equals("Observation.related")) 3576 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code"); 3577 else if (slicer.getPath().equals("Bundle.entry")) 3578 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile"); 3579 else 3580 throw new Error("No slicing for "+slicer.getPath()); 3581 } 3582 3583 public class SpanEntry { 3584 private List<SpanEntry> children = new ArrayList<SpanEntry>(); 3585 private boolean profile; 3586 private String id; 3587 private String name; 3588 private String resType; 3589 private String cardinality; 3590 private String description; 3591 private String profileLink; 3592 private String resLink; 3593 private String type; 3594 3595 public String getName() { 3596 return name; 3597 } 3598 public void setName(String name) { 3599 this.name = name; 3600 } 3601 public String getResType() { 3602 return resType; 3603 } 3604 public void setResType(String resType) { 3605 this.resType = resType; 3606 } 3607 public String getCardinality() { 3608 return cardinality; 3609 } 3610 public void setCardinality(String cardinality) { 3611 this.cardinality = cardinality; 3612 } 3613 public String getDescription() { 3614 return description; 3615 } 3616 public void setDescription(String description) { 3617 this.description = description; 3618 } 3619 public String getProfileLink() { 3620 return profileLink; 3621 } 3622 public void setProfileLink(String profileLink) { 3623 this.profileLink = profileLink; 3624 } 3625 public String getResLink() { 3626 return resLink; 3627 } 3628 public void setResLink(String resLink) { 3629 this.resLink = resLink; 3630 } 3631 public String getId() { 3632 return id; 3633 } 3634 public void setId(String id) { 3635 this.id = id; 3636 } 3637 public boolean isProfile() { 3638 return profile; 3639 } 3640 public void setProfile(boolean profile) { 3641 this.profile = profile; 3642 } 3643 public List<SpanEntry> getChildren() { 3644 return children; 3645 } 3646 public String getType() { 3647 return type; 3648 } 3649 public void setType(String type) { 3650 this.type = type; 3651 } 3652 3653 } 3654 3655 public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException { 3656 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true); 3657 gen.setTranslator(getTranslator()); 3658 TableModel model = initSpanningTable(gen, "", false); 3659 Set<String> processed = new HashSet<String>(); 3660 SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix); 3661 3662 genSpanEntry(gen, model.getRows(), span); 3663 return gen.generate(model, "", 0, outputTracker); 3664 } 3665 3666 private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException { 3667 SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile); 3668 boolean wantProcess = !processed.contains(profile.getUrl()); 3669 processed.add(profile.getUrl()); 3670 if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 3671 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 3672 if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) { 3673 String card = getCardinality(ed, profile.getSnapshot().getElement()); 3674 if (!card.endsWith(".0")) { 3675 List<String> refProfiles = listReferenceProfiles(ed); 3676 if (refProfiles.size() > 0) { 3677 String uri = refProfiles.get(0); 3678 if (uri != null) { 3679 StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri); 3680 if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) { 3681 res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix)); 3682 } 3683 } 3684 } 3685 } 3686 } 3687 } 3688 } 3689 return res; 3690 } 3691 3692 3693 private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) { 3694 int min = ed.getMin(); 3695 int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax()); 3696 while (ed != null && ed.getPath().contains(".")) { 3697 ed = findParent(ed, list); 3698 if (ed.getMax().equals("0")) 3699 max = 0; 3700 else if (!ed.getMax().equals("1") && !ed.hasSlicing()) 3701 max = Integer.MAX_VALUE; 3702 if (ed.getMin() == 0) 3703 min = 0; 3704 } 3705 return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max)); 3706 } 3707 3708 3709 private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) { 3710 int i = list.indexOf(ed)-1; 3711 while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+".")) 3712 i--; 3713 if (i == -1) 3714 return null; 3715 else 3716 return list.get(i); 3717 } 3718 3719 3720 private List<String> listReferenceProfiles(ElementDefinition ed) { 3721 List<String> res = new ArrayList<String>(); 3722 for (TypeRefComponent tr : ed.getType()) { 3723 // code is null if we're dealing with "value" and profile is null if we just have Reference() 3724 if (tr.hasTarget() && tr.hasTargetProfile()) 3725 for (UriType u : tr.getTargetProfile()) 3726 res.add(u.getValue()); 3727 } 3728 return res; 3729 } 3730 3731 3732 private String nameForElement(ElementDefinition ed) { 3733 return ed.getPath().substring(ed.getPath().indexOf(".")+1); 3734 } 3735 3736 3737 private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException { 3738 SpanEntry res = new SpanEntry(); 3739 res.setName(name); 3740 res.setCardinality(cardinality); 3741 res.setProfileLink(profile.getUserString("path")); 3742 res.setResType(profile.getType()); 3743 StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType()); 3744 if (base != null) 3745 res.setResLink(base.getUserString("path")); 3746 res.setId(profile.getId()); 3747 res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT); 3748 StringBuilder b = new StringBuilder(); 3749 b.append(res.getResType()); 3750 boolean first = true; 3751 boolean open = false; 3752 if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 3753 res.setDescription(profile.getName()); 3754 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 3755 if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) { 3756 if (first) { 3757 open = true; 3758 first = false; 3759 b.append("["); 3760 } else { 3761 b.append(", "); 3762 } 3763 b.append(tail(ed.getBase().getPath())); 3764 b.append("="); 3765 b.append(summarize(ed.getFixed())); 3766 } 3767 } 3768 if (open) 3769 b.append("]"); 3770 } else 3771 res.setDescription("Base FHIR "+profile.getName()); 3772 res.setType(b.toString()); 3773 return res ; 3774 } 3775 3776 3777 private String summarize(Type value) throws IOException { 3778 if (value instanceof Coding) 3779 return summarizeCoding((Coding) value); 3780 else if (value instanceof CodeableConcept) 3781 return summarizeCodeableConcept((CodeableConcept) value); 3782 else 3783 return buildJson(value); 3784 } 3785 3786 3787 private String summarizeCoding(Coding value) { 3788 String uri = value.getSystem(); 3789 String system = NarrativeGenerator.describeSystem(uri); 3790 if (Utilities.isURL(system)) { 3791 if (system.equals("http://cap.org/protocols")) 3792 system = "CAP Code"; 3793 } 3794 return system+" "+value.getCode(); 3795 } 3796 3797 3798 private String summarizeCodeableConcept(CodeableConcept value) { 3799 if (value.hasCoding()) 3800 return summarizeCoding(value.getCodingFirstRep()); 3801 else 3802 return value.getText(); 3803 } 3804 3805 3806 private boolean isKeyProperty(String path) { 3807 return Utilities.existsInList(path, "Observation.code"); 3808 } 3809 3810 3811 public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical) { 3812 TableModel model = gen.new TableModel(); 3813 3814 model.setDocoImg(prefix+"help16.png"); 3815 model.setDocoRef(prefix+"formats.html#table"); // todo: change to graph definition 3816 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0)); 3817 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Card.", "Minimum and Maximum # of times the the element can appear in the instance", null, 0)); 3818 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0)); 3819 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0)); 3820 return model; 3821 } 3822 3823 private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException { 3824 Row row = gen.new Row(); 3825 rows.add(row); 3826 row.setAnchor(span.getId()); 3827 //row.setColor(..?); 3828 if (span.isProfile()) 3829 row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE); 3830 else 3831 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 3832 3833 row.getCells().add(gen.new Cell(null, null, span.getName(), null, null)); 3834 row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null)); 3835 row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null)); 3836 row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null)); 3837 3838 for (SpanEntry child : span.getChildren()) 3839 genSpanEntry(gen, row.getSubRows(), child); 3840 } 3841 3842 3843 public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) { 3844 if (discriminator.endsWith("@pattern")) 3845 return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 3846 if (discriminator.endsWith("@profile")) 3847 return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 3848 if (discriminator.endsWith("@type")) 3849 return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6)); 3850 if (discriminator.endsWith("@exists")) 3851 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 3852 if (isExists) 3853 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 3854 return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator); 3855 } 3856 3857 3858 private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) { 3859 return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str); 3860 } 3861 3862 3863 public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException { 3864 switch (t.getType()) { 3865 case PROFILE: return t.getPath()+"/@profile"; 3866 case TYPE: return t.getPath()+"/@type"; 3867 case VALUE: return t.getPath(); 3868 case EXISTS: return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - one with minOccurs=1 and other with maxOccur=0 3869 default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2"); 3870 } 3871 } 3872 3873 3874 public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) { 3875 String epath = url.substring(54); 3876 if (!epath.contains(".")) 3877 return null; 3878 String type = epath.substring(0, epath.indexOf(".")); 3879 StructureDefinition sd = context.fetchTypeDefinition(type); 3880 if (sd == null) 3881 return null; 3882 ElementDefinition ed = null; 3883 for (ElementDefinition t : sd.getSnapshot().getElement()) { 3884 if (t.getPath().equals(epath)) { 3885 ed = t; 3886 break; 3887 } 3888 } 3889 if (ed == null) 3890 return null; 3891 if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) { 3892 return null; 3893 } else { 3894 StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"); 3895 StructureDefinition ext = template.copy(); 3896 ext.setUrl(url); 3897 ext.setId("extension-"+epath); 3898 ext.setName("Extension-"+epath); 3899 ext.setTitle("Extension for r4 "+epath); 3900 ext.setStatus(sd.getStatus()); 3901 ext.setDate(sd.getDate()); 3902 ext.getContact().clear(); 3903 ext.getContact().addAll(sd.getContact()); 3904 ext.setFhirVersion(sd.getFhirVersion()); 3905 ext.setDescription(ed.getDefinition()); 3906 ext.getContext().clear(); 3907 ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf("."))); 3908 ext.getDifferential().getElement().clear(); 3909 ext.getSnapshot().getElement().get(3).setFixed(new UriType(url)); 3910 ext.getSnapshot().getElement().set(4, ed.copy()); 3911 ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary())); 3912 return ext; 3913 } 3914 3915 } 3916 3917 3918 public boolean isThrowException() { 3919 return exception; 3920 } 3921 3922 3923 public void setThrowException(boolean exception) { 3924 this.exception = exception; 3925 } 3926}