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