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