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