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