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