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