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