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