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