001package org.hl7.fhir.r4.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.Iterator; 041import java.util.List; 042import java.util.Map; 043import java.util.Set; 044 045import org.apache.commons.lang3.StringUtils; 046import org.hl7.fhir.exceptions.DefinitionException; 047import org.hl7.fhir.exceptions.FHIRException; 048import org.hl7.fhir.exceptions.FHIRFormatError; 049import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution; 050import org.hl7.fhir.r4.context.IWorkerContext; 051import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult; 052import org.hl7.fhir.r4.elementmodel.ObjectConverter; 053import org.hl7.fhir.r4.elementmodel.Property; 054import org.hl7.fhir.r4.formats.IParser; 055import org.hl7.fhir.r4.model.Base; 056import org.hl7.fhir.r4.model.BooleanType; 057import org.hl7.fhir.r4.model.CanonicalType; 058import org.hl7.fhir.r4.model.CodeType; 059import org.hl7.fhir.r4.model.CodeableConcept; 060import org.hl7.fhir.r4.model.Coding; 061import org.hl7.fhir.r4.model.Element; 062import org.hl7.fhir.r4.model.ElementDefinition; 063import org.hl7.fhir.r4.model.ElementDefinition.AggregationMode; 064import org.hl7.fhir.r4.model.ElementDefinition.DiscriminatorType; 065import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBaseComponent; 066import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 067import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent; 068import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionExampleComponent; 069import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent; 070import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent; 071import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 072import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation; 073import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules; 074import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 075import org.hl7.fhir.r4.model.Enumeration; 076import org.hl7.fhir.r4.model.Enumerations.BindingStrength; 077import org.hl7.fhir.r4.model.Enumerations.FHIRVersion; 078import org.hl7.fhir.r4.model.Extension; 079import org.hl7.fhir.r4.model.IntegerType; 080import org.hl7.fhir.r4.model.PrimitiveType; 081import org.hl7.fhir.r4.model.Quantity; 082import org.hl7.fhir.r4.model.Resource; 083import org.hl7.fhir.r4.model.StringType; 084import org.hl7.fhir.r4.model.StructureDefinition; 085import org.hl7.fhir.r4.model.StructureDefinition.ExtensionContextType; 086import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionContextComponent; 087import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionDifferentialComponent; 088import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 089import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent; 090import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent; 091import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 092import org.hl7.fhir.r4.model.Type; 093import org.hl7.fhir.r4.model.UriType; 094import org.hl7.fhir.r4.model.ValueSet; 095import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; 096import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 097import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 098import org.hl7.fhir.r4.utils.NarrativeGenerator; 099import org.hl7.fhir.r4.utils.ToolingExtensions; 100import org.hl7.fhir.r4.utils.TranslatingUtilities; 101import org.hl7.fhir.r4.utils.formats.CSVWriter; 102import org.hl7.fhir.r4.utils.formats.XLSXWriter; 103import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 104import org.hl7.fhir.utilities.TerminologyServiceOptions; 105import org.hl7.fhir.utilities.Utilities; 106import org.hl7.fhir.utilities.VersionUtilities; 107import org.hl7.fhir.utilities.validation.ValidationMessage; 108import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 109import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 110import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 111import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 112import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 113import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMode; 114import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 115import org.hl7.fhir.utilities.xhtml.XhtmlNode; 116import org.hl7.fhir.utilities.xml.SchematronWriter; 117import org.hl7.fhir.utilities.xml.SchematronWriter.Rule; 118import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType; 119import org.hl7.fhir.utilities.xml.SchematronWriter.Section; 120 121/** 122 * This class provides a set of utility operations for working with Profiles. 123 * Key functionality: 124 * * getChildMap --? 125 * * getChildList 126 * * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 127 * * closeDifferential: fill out a differential by excluding anything not mentioned 128 * * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions 129 * * generateTable: generate the HTML for a hierarchical table presentation of a structure 130 * * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point 131 * * summarize: describe the contents of a profile 132 * 133 * 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 134 * 135 * @author Grahame 136 * 137 */ 138public class ProfileUtilities extends TranslatingUtilities { 139 140 public class ElementRedirection { 141 142 private String path; 143 private ElementDefinition element; 144 145 public ElementRedirection(ElementDefinition element, String path) { 146 this.path = path; 147 this.element = element; 148 } 149 150 public ElementDefinition getElement() { 151 return element; 152 } 153 154 @Override 155 public String toString() { 156 return element.toString() + " : "+path; 157 } 158 159 public String getPath() { 160 return path; 161 } 162 163 } 164 165 public class TypeSlice { 166 private ElementDefinition defn; 167 private String type; 168 public TypeSlice(ElementDefinition defn, String type) { 169 super(); 170 this.defn = defn; 171 this.type = type; 172 } 173 public ElementDefinition getDefn() { 174 return defn; 175 } 176 public String getType() { 177 return type; 178 } 179 180 } 181 private static final int MAX_RECURSION_LIMIT = 10; 182 183 public class ExtensionContext { 184 185 private ElementDefinition element; 186 private StructureDefinition defn; 187 188 public ExtensionContext(StructureDefinition ext, ElementDefinition ed) { 189 this.defn = ext; 190 this.element = ed; 191 } 192 193 public ElementDefinition getElement() { 194 return element; 195 } 196 197 public StructureDefinition getDefn() { 198 return defn; 199 } 200 201 public String getUrl() { 202 if (element == defn.getSnapshot().getElement().get(0)) 203 return defn.getUrl(); 204 else 205 return element.getSliceName(); 206 } 207 208 public ElementDefinition getExtensionValueDefinition() { 209 int i = defn.getSnapshot().getElement().indexOf(element)+1; 210 while (i < defn.getSnapshot().getElement().size()) { 211 ElementDefinition ed = defn.getSnapshot().getElement().get(i); 212 if (ed.getPath().equals(element.getPath())) 213 return null; 214 if (ed.getPath().startsWith(element.getPath()+".value")) 215 return ed; 216 i++; 217 } 218 return null; 219 } 220 } 221 222 private static final String ROW_COLOR_ERROR = "#ffcccc"; 223 private static final String ROW_COLOR_FATAL = "#ff9999"; 224 private static final String ROW_COLOR_WARNING = "#ffebcc"; 225 private static final String ROW_COLOR_HINT = "#ebf5ff"; 226 private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8"; 227 public static final int STATUS_OK = 0; 228 public static final int STATUS_HINT = 1; 229 public static final int STATUS_WARNING = 2; 230 public static final int STATUS_ERROR = 3; 231 public static final int STATUS_FATAL = 4; 232 233 234 private static final String DERIVATION_EQUALS = "derivation.equals"; 235 public static final String DERIVATION_POINTER = "derived.pointer"; 236 public static final String IS_DERIVED = "derived.fact"; 237 public static final String UD_ERROR_STATUS = "error-status"; 238 private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed"; 239 private final boolean ADD_REFERENCE_TO_TABLE = true; 240 241 private boolean useTableForFixedValues = true; 242 private boolean debug; 243 244 // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here 245 private final IWorkerContext context; 246 private List<ValidationMessage> messages; 247 private List<String> snapshotStack = new ArrayList<String>(); 248 private ProfileKnowledgeProvider pkp; 249 private boolean igmode; 250 private boolean exception; 251 private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions(); 252 private boolean newSlicingProcessing; 253 254 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) { 255 super(); 256 this.context = context; 257 this.messages = messages; 258 this.pkp = pkp; 259 } 260 261 private class UnusedTracker { 262 private boolean used; 263 } 264 265 public boolean isIgmode() { 266 return igmode; 267 } 268 269 270 public void setIgmode(boolean igmode) { 271 this.igmode = igmode; 272 } 273 274 public interface ProfileKnowledgeProvider { 275 public class BindingResolution { 276 public String display; 277 public String url; 278 } 279 public boolean isDatatype(String typeSimple); 280 public boolean isResource(String typeSimple); 281 public boolean hasLinkFor(String typeSimple); 282 public String getLinkFor(String corePath, String typeSimple); 283 public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path) throws FHIRException; 284 public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException; 285 public String getLinkForProfile(StructureDefinition profile, String url); 286 public boolean prependLinks(); 287 public String getLinkForUrl(String corePath, String s); 288 } 289 290 291 292 public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException { 293 if (element.getContentReference()!=null) { 294 for (ElementDefinition e : profile.getSnapshot().getElement()) { 295 if (element.getContentReference().equals("#"+e.getId())) 296 return getChildMap(profile, e); 297 } 298 throw new DefinitionException("Unable to resolve name reference "+element.getContentReference()+" at path "+element.getPath()); 299 300 } else { 301 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 302 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 303 String path = element.getPath(); 304 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 305 ElementDefinition e = elements.get(index); 306 if (e.getPath().startsWith(path + ".")) { 307 // We only want direct children, not all descendants 308 if (!e.getPath().substring(path.length()+1).contains(".")) 309 res.add(e); 310 } else 311 break; 312 } 313 return res; 314 } 315 } 316 317 318 public static List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException { 319 if (!element.hasSlicing()) 320 throw new Error("getSliceList should only be called when the element has slicing"); 321 322 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 323 List<ElementDefinition> elements = profile.getSnapshot().getElement(); 324 String path = element.getPath(); 325 for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) { 326 ElementDefinition e = elements.get(index); 327 if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) { 328 // We want elements with the same path (until we hit an element that doesn't start with the same path) 329 if (e.getPath().equals(element.getPath())) 330 res.add(e); 331 } else 332 break; 333 } 334 return res; 335 } 336 337 338 /** 339 * Given a Structure, navigate to the element given by the path and return the direct children of that element 340 * 341 * @param structure The structure to navigate into 342 * @param path The path of the element within the structure to get the children for 343 * @return A List containing the element children (all of them are Elements) 344 */ 345 public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) { 346 return getChildList(profile, path, id, false); 347 } 348 349 public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff) { 350 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 351 352 boolean capturing = id==null; 353 if (id==null && !path.contains(".")) 354 capturing = true; 355 356 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 357 for (ElementDefinition e : list) { 358 if (e == null) 359 throw new Error("element = null: "+profile.getUrl()); 360 if (e.getId() == null) 361 throw new Error("element id = null: "+e.toString()+" on "+profile.getUrl()); 362 363 if (!capturing && id!=null && e.getId().equals(id)) { 364 capturing = true; 365 } 366 367 // If our element is a slice, stop capturing children as soon as we see the next slice 368 if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path)) 369 break; 370 371 if (capturing) { 372 String p = e.getPath(); 373 374 if (!Utilities.noString(e.getContentReference()) && path.startsWith(p)) { 375 if (path.length() > p.length()) 376 return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff); 377 else 378 return getChildList(profile, e.getContentReference(), null, diff); 379 380 } else if (p.startsWith(path+".") && !p.equals(path)) { 381 String tail = p.substring(path.length()+1); 382 if (!tail.contains(".")) { 383 res.add(e); 384 } 385 } 386 } 387 } 388 389 return res; 390 } 391 392 public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) { 393 return getChildList(structure, element.getPath(), element.getId(), diff); 394 } 395 396 public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) { 397 return getChildList(structure, element.getPath(), element.getId(), false); 398 } 399 400 public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException { 401 if (base == null) 402 throw new DefinitionException("no base profile provided"); 403 if (derived == null) 404 throw new DefinitionException("no derived structure provided"); 405 406 for (StructureDefinitionMappingComponent baseMap : base.getMapping()) { 407 boolean found = false; 408 for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) { 409 if (derivedMap.getUri() != null && derivedMap.getUri().equals(baseMap.getUri())) { 410 found = true; 411 break; 412 } 413 } 414 if (!found) 415 derived.getMapping().add(baseMap); 416 } 417 } 418 419 /** 420 * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile 421 * 422 * @param base - the base structure on which the differential will be applied 423 * @param differential - the differential to apply to the base 424 * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL (e.g. the canonical URL) 425 * @param webUrl - where the base has relative urls in markdown, these need to be converted to absolutes by prepending this URL (this is not the same as the canonical URL) 426 * @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 427 * @return 428 * @throws FHIRException 429 * @throws DefinitionException 430 * @throws Exception 431 */ 432 public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException { 433 if (base == null) 434 throw new DefinitionException("no base profile provided"); 435 if (derived == null) 436 throw new DefinitionException("no derived structure provided"); 437 438 if (snapshotStack.contains(derived.getUrl())) 439 throw new DefinitionException("Circular snapshot references detected; cannot generate snapshot (stack = "+snapshotStack.toString()+")"); 440 snapshotStack.add(derived.getUrl()); 441 442 if (!Utilities.noString(webUrl) && !webUrl.endsWith("/")) 443 webUrl = webUrl + '/'; 444 445 derived.setSnapshot(new StructureDefinitionSnapshotComponent()); 446 447 try { 448 // so we have two lists - the base list, and the differential list 449 // the differential list is only allowed to include things that are in the base list, but 450 // is allowed to include them multiple times - thereby slicing them 451 452 // our approach is to walk through the base list, and see whether the differential 453 // says anything about them. 454 int baseCursor = 0; 455 int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths 456 457 if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) 458 throw new Error("type on first differential element!"); 459 460 for (ElementDefinition e : derived.getDifferential().getElement()) 461 e.clearUserData(GENERATED_IN_SNAPSHOT); 462 463 // we actually delegate the work to a subroutine so we can re-enter it with a different cursors 464 StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential()); // we make a copy here because we're sometimes going to hack the differential while processing it. Have to migrate user data back afterwards 465 466 processPaths("", derived.getSnapshot(), base.getSnapshot(), diff, baseCursor, diffCursor, base.getSnapshot().getElement().size()-1, 467 derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, webUrl, derived.present(), null, null, false, base.getUrl(), null, false, new ArrayList<ElementRedirection>(), base); 468 if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty()) 469 throw new Error("type on first snapshot element for "+derived.getSnapshot().getElementFirstRep().getPath()+" in "+derived.getUrl()+" from "+base.getUrl()); 470 updateMaps(base, derived); 471 472 if (debug) { 473 System.out.println("Differential: "); 474 for (ElementDefinition ed : derived.getDifferential().getElement()) 475 System.out.println(" "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" id = "+ed.getId()+" "+constraintSummary(ed)); 476 System.out.println("Snapshot: "); 477 for (ElementDefinition ed : derived.getSnapshot().getElement()) 478 System.out.println(" "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" id = "+ed.getId()+" "+constraintSummary(ed)); 479 } 480 setIds(derived, false); 481 //Check that all differential elements have a corresponding snapshot element 482 for (ElementDefinition e : diff.getElement()) { 483 if (!e.hasUserData("diff-source")) 484 throw new Error("Unxpected internal condition - no source on diff element"); 485 else { 486 if (e.hasUserData(DERIVATION_EQUALS)) 487 ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_EQUALS, e.getUserData(DERIVATION_EQUALS)); 488 if (e.hasUserData(DERIVATION_POINTER)) 489 ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_POINTER, e.getUserData(DERIVATION_POINTER)); 490 } 491 if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) { 492 System.out.println("Error in snapshot generation: Differential for "+derived.getUrl()+" with " + (e.hasId() ? "id: "+e.getId() : "path: "+e.getPath())+" has an element that is not marked with a snapshot match"); 493 if (exception) 494 throw new DefinitionException("Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has "+(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath())); 495 else 496 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, "Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has id: " + e.getId(), ValidationMessage.IssueSeverity.ERROR)); 497 } 498 } 499 if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 500 for (ElementDefinition ed : derived.getSnapshot().getElement()) { 501 if (!ed.hasBase()) { 502 ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax()); 503 } 504 } 505 } 506 } catch (Exception e) { 507 // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind 508 derived.setSnapshot(null); 509 throw e; 510 } 511 } 512 513 private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) { 514 StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent(); 515 for (ElementDefinition sed : source.getElement()) { 516 ElementDefinition ted = sed.copy(); 517 diff.getElement().add(ted); 518 ted.setUserData("diff-source", sed); 519 } 520 return diff; 521 } 522 523 524 private String constraintSummary(ElementDefinition ed) { 525 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 526 if (ed.hasPattern()) 527 b.append("pattern="+ed.getPattern().fhirType()); 528 if (ed.hasFixed()) 529 b.append("fixed="+ed.getFixed().fhirType()); 530 if (ed.hasConstraint()) 531 b.append("constraints="+ed.getConstraint().size()); 532 return b.toString(); 533 } 534 535 536 private String sliceSummary(ElementDefinition ed) { 537 if (!ed.hasSlicing() && !ed.hasSliceName()) 538 return ""; 539 if (ed.hasSliceName()) 540 return " (slicename = "+ed.getSliceName()+")"; 541 542 StringBuilder b = new StringBuilder(); 543 boolean first = true; 544 for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) { 545 if (first) 546 first = false; 547 else 548 b.append("|"); 549 b.append(d.getPath()); 550 } 551 return " (slicing by "+b.toString()+")"; 552 } 553 554 555 private String typeSummary(ElementDefinition ed) { 556 StringBuilder b = new StringBuilder(); 557 boolean first = true; 558 for (TypeRefComponent tr : ed.getType()) { 559 if (first) 560 first = false; 561 else 562 b.append("|"); 563 b.append(tr.getWorkingCode()); 564 } 565 return b.toString(); 566 } 567 568 private String typeSummaryWithProfile(ElementDefinition ed) { 569 StringBuilder b = new StringBuilder(); 570 boolean first = true; 571 for (TypeRefComponent tr : ed.getType()) { 572 if (first) 573 first = false; 574 else 575 b.append("|"); 576 b.append(tr.getWorkingCode()); 577 if (tr.hasProfile()) { 578 b.append("("); 579 b.append(tr.getProfile()); 580 b.append(")"); 581 582 } 583 } 584 return b.toString(); 585 } 586 587 588 private boolean findMatchingElement(String id, List<ElementDefinition> list) { 589 for (ElementDefinition ed : list) { 590 if (ed.getId().equals(id)) 591 return true; 592 if (id.endsWith("[x]")) { 593 if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains(".")) 594 return true; 595 } 596 } 597 return false; 598 } 599 600 601 /** 602 * @param trimDifferential 603 * @param srcSD 604 * @throws DefinitionException, FHIRException 605 * @throws Exception 606 */ 607 private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit, 608 int diffLimit, String url, String webUrl, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone, List<ElementRedirection> redirector, StructureDefinition srcSD) throws DefinitionException, FHIRException { 609 if (debug) 610 System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", redirector = "+(redirector == null ? "null" : redirector.toString())+")"); 611 ElementDefinition res = null; 612 List<TypeSlice> typeList = new ArrayList<>(); 613 // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries) 614 while (baseCursor <= baseLimit) { 615 // get the current focus of the base, and decide what to do 616 ElementDefinition currentBase = base.getElement().get(baseCursor); 617 String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector); 618 if (debug) 619 System.out.println(indent+" - "+cpath+": base = "+baseCursor+" ("+descED(base.getElement(),baseCursor)+") to "+baseLimit+" ("+descED(base.getElement(),baseLimit)+"), diff = "+diffCursor+" ("+descED(differential.getElement(),diffCursor)+") to "+diffLimit+" ("+descED(differential.getElement(),diffLimit)+") "+ 620 "(slicingDone = "+slicingDone+") (diffpath= "+(differential.getElement().size() > diffCursor ? differential.getElement().get(diffCursor).getPath() : "n/a")+")"); 621 List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get a list of matching elements in scope 622 623 // in the simple case, source is not sliced. 624 if (!currentBase.hasSlicing()) { 625 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 626 // so we just copy it in 627 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 628 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 629 updateFromBase(outcome, currentBase); 630 markDerived(outcome); 631 if (resultPathBase == null) 632 resultPathBase = outcome.getPath(); 633 else if (!outcome.getPath().startsWith(resultPathBase)) 634 throw new DefinitionException("Adding wrong path"); 635 result.getElement().add(outcome); 636 if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), true)) { 637 // well, the profile walks into this, so we need to as well 638 // did we implicitly step into a new type? 639 if (baseHasChildren(base, currentBase)) { // not a new type here 640 processPaths(indent+" ", result, base, differential, baseCursor+1, diffCursor, baseLimit, diffLimit, url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, redirector, srcSD); 641 baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor+1, baseLimit); 642 } else { 643 if (outcome.getType().size() == 0) { 644 throw new DefinitionException(diffMatches.get(0).getPath()+" has no children ("+differential.getElement().get(diffCursor).getPath()+") and no types in profile "+profileName); 645 } 646 if (outcome.getType().size() > 1) { 647 for (TypeRefComponent t : outcome.getType()) { 648 if (!t.getWorkingCode().equals("Reference")) 649 throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName); 650 } 651 } 652 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 653 if (dt == null) 654 throw new DefinitionException("Unknown type "+outcome.getType().get(0)+" at "+diffMatches.get(0).getPath()); 655 contextName = dt.getUrl(); 656 int start = diffCursor; 657 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+".")) 658 diffCursor++; 659 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, 660 diffCursor-1, url, webUrl, profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD); 661 } 662 } 663 baseCursor++; 664 } else if (diffMatches.size() == 1 && (slicingDone || (!isImplicitSlicing(diffMatches.get(0), cpath) && !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName()))))) {// one matching element in the differential 665 ElementDefinition template = null; 666 if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 && diffMatches.get(0).getType().get(0).hasProfile() && !"Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode())) { 667 CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0); 668 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue()); 669 if (sd != null) { 670 if (!sd.hasSnapshot()) { 671 StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 672 if (sdb == null) 673 throw new DefinitionException("no base for "+sd.getBaseDefinition()); 674 generateSnapshot(sdb, sd, sd.getUrl(), (sdb.hasUserData("path")) ? Utilities.extractBaseUrl(sdb.getUserString("path")) : webUrl, sd.getName()); 675 } 676 ElementDefinition src; 677 if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) { 678 src = null; 679 String eid = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT); 680 for (ElementDefinition t : sd.getSnapshot().getElement()) { 681 if (eid.equals(t.getId())) 682 src = t; 683 } 684 if (src == null) 685 throw new DefinitionException("Unable to find element "+eid+" in "+p.getValue()); 686 } else 687 src = sd.getSnapshot().getElement().get(0); 688 template = src.copy().setPath(currentBase.getPath()); 689 template.setSliceName(null); 690 // temporary work around 691 if (!"Extension".equals(diffMatches.get(0).getType().get(0).getCode())) { 692 template.setMin(currentBase.getMin()); 693 template.setMax(currentBase.getMax()); 694 } 695 } 696 } 697 if (template == null) 698 template = currentBase.copy(); 699 else 700 // some of what's in currentBase overrides template 701 template = overWriteWithCurrent(template, currentBase); 702 703 ElementDefinition outcome = updateURLs(url, webUrl, template); 704 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 705 if (res == null) 706 res = outcome; 707 updateFromBase(outcome, currentBase); 708 if (diffMatches.get(0).hasSliceName()) 709 outcome.setSliceName(diffMatches.get(0).getSliceName()); 710 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD); 711 removeStatusExtensions(outcome); 712// if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*") && !diffMatches.get(0).hasSlicing()) // if the base profile allows multiple types, but the profile only allows one, rename it 713// outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode())); 714 outcome.setSlicing(null); 715 if (resultPathBase == null) 716 resultPathBase = outcome.getPath(); 717 else if (!outcome.getPath().startsWith(resultPathBase)) 718 throw new DefinitionException("Adding wrong path"); 719 result.getElement().add(outcome); 720 baseCursor++; 721 diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1; 722 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 723 if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) { 724 if (outcome.getType().size() > 1) { 725 if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) { 726 String en = tail(outcome.getPath()); 727 String tn = tail(diffMatches.get(0).getPath()); 728 String t = tn.substring(en.length()-3); 729 if (isPrimitive(Utilities.uncapitalize(t))) 730 t = Utilities.uncapitalize(t); 731 List<TypeRefComponent> ntr = getByTypeName(outcome.getType(), t); // keep any additional information 732 if (ntr.isEmpty()) 733 ntr.add(new TypeRefComponent().setCode(t)); 734 outcome.getType().clear(); 735 outcome.getType().addAll(ntr); 736 } 737 if (outcome.getType().size() > 1) 738 for (TypeRefComponent t : outcome.getType()) { 739 if (!t.getCode().equals("Reference")) { 740 boolean nonExtension = false; 741 for (ElementDefinition ed : diffMatches) 742 if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension")) 743 nonExtension = true; 744 if (nonExtension) 745 throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName); 746 } 747 } 748 } 749 int start = diffCursor; 750 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) 751 diffCursor++; 752 if (outcome.hasContentReference()) { 753 ElementDefinition tgt = getElementById(base.getElement(), outcome.getContentReference()); 754 if (tgt == null) 755 throw new DefinitionException("Unable to resolve reference to "+outcome.getContentReference()); 756 replaceFromContentReference(outcome, tgt); 757 int nbc = base.getElement().indexOf(tgt)+1; 758 int nbl = nbc; 759 while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getPath()+".")) 760 nbl++; 761 processPaths(indent+" ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, redirectorStack(redirector, outcome, cpath), srcSD); 762 } else { 763 StructureDefinition dt = outcome.getType().size() == 1 ? getProfileForDataType(outcome.getType().get(0)) : getProfileForDataType("Element"); 764 if (dt == null) 765 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"); 766 contextName = dt.getUrl(); 767 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, 768 diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, new ArrayList<ElementRedirection>(), srcSD); 769 } 770 } 771 } 772 } else if (diffsConstrainTypes(diffMatches, cpath, typeList)) { 773 int start = 0; 774 int nbl = findEndOfElement(base, baseCursor); 775 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 776 ElementDefinition elementToRemove = null; 777 // we come here whether they are sliced in the diff, or whether the short cut is used. 778 if (typeList.get(0).type != null) { 779 // this is the short cut method, we've just dived in and specified a type slice. 780 // in R3 (and unpatched R4, as a workaround right now... 781 if (!VersionUtilities.isR4Plus(context.getVersion()) || !newSlicingProcessing) { // newSlicingProcessing is a work around for editorial loop dependency 782 // we insert a cloned element with the right types at the start of the diffMatches 783 ElementDefinition ed = new ElementDefinition(); 784 ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath)); 785 for (TypeSlice ts : typeList) 786 ed.addType().setCode(ts.type); 787 ed.setSlicing(new ElementDefinitionSlicingComponent()); 788 ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 789 ed.getSlicing().setRules(SlicingRules.CLOSED); 790 ed.getSlicing().setOrdered(false); 791 diffMatches.add(0, ed); 792 differential.getElement().add(ndc, ed); 793 elementToRemove = ed; 794 } else { 795 // as of R4, this changed; if there's no slice, there's no constraint on the slice types, only one the type. 796 // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type. 797 // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element 798 ElementDefinition ed = new ElementDefinition(); 799 ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath)); 800 ed.setSlicing(new ElementDefinitionSlicingComponent()); 801 ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 802 ed.getSlicing().setRules(SlicingRules.CLOSED); 803 ed.getSlicing().setOrdered(false); 804 diffMatches.add(0, ed); 805 differential.getElement().add(ndc, ed); 806 elementToRemove = ed; 807 } 808 } 809 int ndl = findEndOfElement(differential, ndc); 810 // the first element is setting up the slicing 811 if (diffMatches.get(0).getSlicing().hasRules()) 812 if (diffMatches.get(0).getSlicing().getRules() != SlicingRules.CLOSED) 813 throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.rules != closed"); 814 if (diffMatches.get(0).getSlicing().hasOrdered()) 815 if (diffMatches.get(0).getSlicing().getOrdered()) 816 throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.ordered = true"); 817 if (diffMatches.get(0).getSlicing().hasDiscriminator()) { 818 if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1) 819 throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.count() > 1"); 820 if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath())) 821 throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.path != '$this'"); 822 if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE) 823 throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.type != 'type'"); 824 } 825 // check the slice names too while we're at it... 826 for (TypeSlice ts : typeList) 827 if (ts.type != null) { 828 String tn = rootName(cpath)+Utilities.capitalize(ts.type); 829 if (!ts.defn.hasSliceName()) 830 ts.defn.setSliceName(tn); 831 else if (!ts.defn.getSliceName().equals(tn)) 832 throw new FHIRException("Error at path "+(!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)+": Slice name must be '"+tn+"' but is '"+ts.defn.getSliceName()+"'"); 833 if (!ts.defn.hasType()) 834 ts.defn.addType().setCode(ts.type); 835 else if (ts.defn.getType().size() > 1) 836 throw new FHIRException("Error at path "+(!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)+": Slice for type '"+tn+"' has more than one type '"+ts.defn.typeSummary()+"'"); 837 else if (!ts.defn.getType().get(0).getCode().equals(ts.type)) 838 throw new FHIRException("Error at path "+(!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)+": Slice for type '"+tn+"' has wrong type '"+ts.defn.typeSummary()+"'"); 839 } 840 841 // ok passed the checks. 842 // copy the root diff, and then process any children it has 843 ElementDefinition e = processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 844 trimDifferential, contextName, resultPathBase, true, redirector, srcSD); 845 if (e==null) 846 throw new FHIRException("Did not find type root: " + diffMatches.get(0).getPath()); 847 // now set up slicing on the e (cause it was wiped by what we called. 848 e.setSlicing(new ElementDefinitionSlicingComponent()); 849 e.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); 850 e.getSlicing().setRules(SlicingRules.CLOSED); 851 e.getSlicing().setOrdered(false); 852 start++; 853 // now process the siblings, which should each be type constrained - and may also have their own children 854 // now we process the base scope repeatedly for each instance of the item in the differential list 855 for (int i = start; i < diffMatches.size(); i++) { 856 // our processing scope for the differential is the item in the list, and all the items before the next one in the list 857 ndc = differential.getElement().indexOf(diffMatches.get(i)); 858 ndl = findEndOfElement(differential, ndc); 859 processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, redirector, srcSD); 860 } 861 if (elementToRemove != null) { 862 differential.getElement().remove(elementToRemove); 863 ndl--; 864 } 865 866 // ok, done with that - next in the base list 867 baseCursor = nbl+1; 868 diffCursor = ndl+1; 869 870 } else { 871 // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct 872 if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0))) 873 // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1 874 // (but you might do that in order to split up constraints by type) 875 throw new DefinitionException("Attempt to a slice an element that does not repeat: "+currentBase.getPath()+"/"+currentBase.getPath()+" from "+contextName+" in "+url); 876 if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error 877 throw new DefinitionException("Differential does not have a slice: "+currentBase.getPath()+"/ (b:"+baseCursor+" of "+ baseLimit+" / "+ diffCursor +"/ "+diffLimit+") in profile "+url); 878 879 // well, if it passed those preconditions then we slice the dest. 880 int start = 0; 881 int nbl = findEndOfElement(base, baseCursor); 882// if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) { 883 if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && (nbl > baseCursor || differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1)) { // there's a default set before the slices 884 int ndc = differential.getElement().indexOf(diffMatches.get(0)); 885 int ndl = findEndOfElement(differential, ndc); 886 ElementDefinition e = processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 887 trimDifferential, contextName, resultPathBase, true, redirector, srcSD); 888 if (e==null) 889 throw new FHIRException("Did not find single slice: " + diffMatches.get(0).getPath()); 890 e.setSlicing(diffMatches.get(0).getSlicing()); 891 start++; 892 } else { 893 // we're just going to accept the differential slicing at face value 894 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 895 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 896 updateFromBase(outcome, currentBase); 897 898 if (!diffMatches.get(0).hasSlicing()) 899 outcome.setSlicing(makeExtensionSlicing()); 900 else 901 outcome.setSlicing(diffMatches.get(0).getSlicing().copy()); 902 if (!outcome.getPath().startsWith(resultPathBase)) 903 throw new DefinitionException("Adding wrong path"); 904 result.getElement().add(outcome); 905 906 // 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. 907 if (!diffMatches.get(0).hasSliceName()) { 908 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD); 909 removeStatusExtensions(outcome); 910 if (!outcome.hasContentReference() && !outcome.hasType()) { 911 throw new DefinitionException("not done yet"); 912 } 913 start++; 914 // result.getElement().remove(result.getElement().size()-1); 915 } else 916 checkExtensionDoco(outcome); 917 } 918 // now, for each entry in the diff matches, we're going to process the base item 919 // our processing scope for base is all the children of the current path 920 int ndc = diffCursor; 921 int ndl = diffCursor; 922 for (int i = start; i < diffMatches.size(); i++) { 923 // our processing scope for the differential is the item in the list, and all the items before the next one in the list 924 ndc = differential.getElement().indexOf(diffMatches.get(i)); 925 ndl = findEndOfElement(differential, ndc); 926/* if (skipSlicingElement && i == 0) { 927 ndc = ndc + 1; 928 if (ndc > ndl) 929 continue; 930 }*/ 931 // now we process the base scope repeatedly for each instance of the item in the differential list 932 processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, redirector, srcSD); 933 } 934 // ok, done with that - next in the base list 935 baseCursor = nbl+1; 936 diffCursor = ndl+1; 937 } 938 } else { 939 // the item is already sliced in the base profile. 940 // here's the rules 941 // 1. irrespective of whether the slicing is ordered or not, the definition order must be maintained 942 // 2. slice element names have to match. 943 // 3. new slices must be introduced at the end 944 // corallory: you can't re-slice existing slices. is that ok? 945 946 // we're going to need this: 947 String path = currentBase.getPath(); 948 ElementDefinition original = currentBase; 949 950 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 951 // copy across the currentbase, and all of its children and siblings 952 while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) { 953 ElementDefinition outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy()); 954 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 955 if (!outcome.getPath().startsWith(resultPathBase)) 956 throw new DefinitionException("Adding wrong path in profile " + profileName + ": "+outcome.getPath()+" vs " + resultPathBase); 957 result.getElement().add(outcome); // so we just copy it in 958 baseCursor++; 959 } 960 } else { 961 // first - check that the slicing is ok 962 boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED; 963 int diffpos = 0; 964 boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension"); 965 if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing 966// if (!isExtension) 967// diffpos++; // if there's a slice on the first, we'll ignore any content it has 968 ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing(); 969 ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing(); 970 if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement())) 971 throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - order @ "+path+" ("+contextName+")"); 972 if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator())) 973 throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - discriminator @ "+path+" ("+contextName+")"); 974 if (!ruleMatches(dSlice.getRules(), bSlice.getRules())) 975 throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - rule @ "+path+" ("+contextName+")"); 976 } 977 ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy()); 978 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 979 updateFromBase(outcome, currentBase); 980 if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) { 981 updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing()); 982 updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url, srcSD); // if there's no slice, we don't want to update the unsliced description 983 removeStatusExtensions(outcome); 984 } else if (!diffMatches.get(0).hasSliceName()) 985 diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called 986 987 result.getElement().add(outcome); 988 989 if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice 990 diffpos++; 991 } 992 if (hasInnerDiffMatches(differential, cpath, diffpos, diffLimit, base.getElement(), false)) { 993 int nbl = findEndOfElement(base, baseCursor); 994 int ndc = differential.getElement().indexOf(diffMatches.get(0))+1; 995 int ndl = findEndOfElement(differential, ndc); 996 processPaths(indent+" ", result, base, differential, baseCursor+1, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, srcSD); 997// throw new Error("Not done yet"); 998// } else if (currentBase.getType().get(0).getCode().equals("BackboneElement") && diffMatches.size() > 0 && diffMatches.get(0).hasSliceName()) { 999 } else if (currentBase.getType().get(0).getCode().equals("BackboneElement")) { 1000 // We need to copy children of the backbone element before we start messing around with slices 1001 int nbl = findEndOfElement(base, baseCursor); 1002 for (int i = baseCursor+1; i<=nbl; i++) { 1003 outcome = updateURLs(url, webUrl, base.getElement().get(i).copy()); 1004 result.getElement().add(outcome); 1005 } 1006 } 1007 1008 // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff. 1009 List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase); 1010 for (ElementDefinition baseItem : baseMatches) { 1011 baseCursor = base.getElement().indexOf(baseItem); 1012 outcome = updateURLs(url, webUrl, baseItem.copy()); 1013 updateFromBase(outcome, currentBase); 1014 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1015 outcome.setSlicing(null); 1016 if (!outcome.getPath().startsWith(resultPathBase)) 1017 throw new DefinitionException("Adding wrong path"); 1018 if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) { 1019 // if there's a diff, we update the outcome with diff 1020 // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url); 1021 //then process any children 1022 int nbl = findEndOfElement(base, baseCursor); 1023 int ndc = differential.getElement().indexOf(diffMatches.get(diffpos)); 1024 int ndl = findEndOfElement(differential, ndc); 1025 // now we process the base scope repeatedly for each instance of the item in the differential list 1026 processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, resultPathBase, true, redirector, srcSD); 1027 // ok, done with that - now set the cursors for if this is the end 1028 baseCursor = nbl; 1029 diffCursor = ndl+1; 1030 diffpos++; 1031 } else { 1032 result.getElement().add(outcome); 1033 baseCursor++; 1034 // just copy any children on the base 1035 while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) { 1036 outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy()); 1037 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1038 if (!outcome.getPath().startsWith(resultPathBase)) 1039 throw new DefinitionException("Adding wrong path"); 1040 result.getElement().add(outcome); 1041 baseCursor++; 1042 } 1043 //Lloyd - add this for test T15 1044 baseCursor--; 1045 } 1046 } 1047 // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed 1048 if (closed && diffpos < diffMatches.size()) 1049 throw new DefinitionException("The base snapshot marks a slicing as closed, but the differential tries to extend it in "+profileName+" at "+path+" ("+cpath+")"); 1050 if (diffpos == diffMatches.size()) { 1051//Lloyd This was causing problems w/ Telus 1052// diffCursor++; 1053 } else { 1054 while (diffpos < diffMatches.size()) { 1055 ElementDefinition diffItem = diffMatches.get(diffpos); 1056 for (ElementDefinition baseItem : baseMatches) 1057 if (baseItem.getSliceName().equals(diffItem.getSliceName())) 1058 throw new DefinitionException("Named items are out of order in the slice"); 1059 outcome = updateURLs(url, webUrl, currentBase.copy()); 1060 // outcome = updateURLs(url, diffItem.copy()); 1061 outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc)); 1062 updateFromBase(outcome, currentBase); 1063 outcome.setSlicing(null); 1064 if (!outcome.getPath().startsWith(resultPathBase)) 1065 throw new DefinitionException("Adding wrong path"); 1066 result.getElement().add(outcome); 1067 updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url, srcSD); 1068 removeStatusExtensions(outcome); 1069 // --- LM Added this 1070 diffCursor = differential.getElement().indexOf(diffItem)+1; 1071 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 1072 if (!baseWalksInto(base.getElement(), baseCursor)) { 1073 if (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) { 1074 if (outcome.getType().size() > 1) 1075 for (TypeRefComponent t : outcome.getType()) { 1076 if (!t.getCode().equals("Reference")) 1077 throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName); 1078 } 1079 TypeRefComponent t = outcome.getType().get(0); 1080 if (t.getCode().equals("BackboneElement")) { 1081 int baseStart = base.getElement().indexOf(currentBase)+1; 1082 int baseMax = baseStart + 1; 1083 while (baseMax < base.getElement().size() && base.getElement().get(baseMax).getPath().startsWith(currentBase.getPath()+".")) 1084 baseMax++; 1085 int start = diffCursor; 1086 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) 1087 diffCursor++; 1088 processPaths(indent+" ", result, base, differential, baseStart, start-1, baseMax-1, 1089 diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), base.getElement().get(0).getPath(), base.getElement().get(0).getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD); 1090 1091 } else { 1092 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 1093 // if (t.getCode().equals("Extension") && t.hasProfile() && !t.getProfile().contains(":")) { 1094 // lloydfix dt = 1095 // } 1096 if (dt == null) 1097 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"); 1098 contextName = dt.getUrl(); 1099 int start = diffCursor; 1100 while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) 1101 diffCursor++; 1102 processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1, 1103 diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD); 1104 } 1105 } else if (outcome.getType().get(0).getCode().equals("Extension")) { 1106 // 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) 1107 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 1108 for (ElementDefinition extEd : dt.getSnapshot().getElement()) { 1109 // We only want the children that aren't the root 1110 if (extEd.getPath().contains(".")) { 1111 ElementDefinition extUrlEd = updateURLs(url, webUrl, extEd.copy()); 1112 extUrlEd.setPath(fixedPathDest(outcome.getPath(), extUrlEd.getPath(), redirector, null)); 1113 // updateFromBase(extUrlEd, currentBase); 1114 markDerived(extUrlEd); 1115 result.getElement().add(extUrlEd); 1116 } 1117 } 1118 } 1119 } 1120 } 1121 // --- 1122 diffpos++; 1123 } 1124 } 1125 baseCursor++; 1126 } 1127 } 1128 } 1129 1130 int i = 0; 1131 for (ElementDefinition e : result.getElement()) { 1132 i++; 1133 if (e.hasMinElement() && e.getMinElement().getValue()==null) 1134 throw new Error("null min"); 1135 } 1136 return res; 1137 } 1138 1139 1140 private void removeStatusExtensions(ElementDefinition outcome) { 1141 outcome.removeExtension(ToolingExtensions.EXT_FMM_LEVEL); 1142 outcome.removeExtension(ToolingExtensions.EXT_STANDARDS_STATUS); 1143 outcome.removeExtension(ToolingExtensions.EXT_NORMATIVE_VERSION); 1144 outcome.removeExtension(ToolingExtensions.EXT_WORKGROUP); 1145 } 1146 1147 1148 private String descED(List<ElementDefinition> list, int index) { 1149 return index >=0 && index < list.size() ? list.get(index).present() : "X"; 1150 } 1151 1152 1153 private boolean baseHasChildren(StructureDefinitionSnapshotComponent base, ElementDefinition ed) { 1154 int index = base.getElement().indexOf(ed); 1155 if (index == -1 || index >= base.getElement().size()-1) 1156 return false; 1157 String p = base.getElement().get(index+1).getPath(); 1158 return isChildOf(p, ed.getPath()); 1159 } 1160 1161 1162 private boolean isChildOf(String sub, String focus) { 1163 if (focus.endsWith("[x]")) { 1164 focus = focus.substring(0, focus.length()-3); 1165 return sub.startsWith(focus); 1166 } else 1167 return sub.startsWith(focus+"."); 1168 } 1169 1170 1171 private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, int baseLimit) { 1172 return baseLimit+1; 1173 } 1174 1175 1176 private String rootName(String cpath) { 1177 String t = tail(cpath); 1178 return t.replace("[x]", ""); 1179 } 1180 1181 1182 private String determineTypeSlicePath(String path, String cpath) { 1183 String headP = path.substring(0, path.lastIndexOf(".")); 1184// String tailP = path.substring(path.lastIndexOf(".")+1); 1185 String tailC = cpath.substring(cpath.lastIndexOf(".")+1); 1186 return headP+"."+tailC; 1187 } 1188 1189 1190 private boolean isImplicitSlicing(ElementDefinition ed, String path) { 1191 if (ed == null || ed.getPath() == null || path == null) 1192 return false; 1193 if (path.equals(ed.getPath())) 1194 return false; 1195 boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3)); 1196 return ok; 1197 } 1198 1199 1200 private boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) { 1201// if (diffMatches.size() < 2) 1202// return false; 1203 String p = diffMatches.get(0).getPath(); 1204 if (!p.endsWith("[x]") && !cPath.endsWith("[x]")) 1205 return false; 1206 typeList.clear(); 1207 String rn = tail(cPath); 1208 rn = rn.substring(0, rn.length()-3); 1209 for (int i = 0; i < diffMatches.size(); i++) { 1210 ElementDefinition ed = diffMatches.get(i); 1211 String n = tail(ed.getPath()); 1212 if (!n.startsWith(rn)) 1213 return false; 1214 String s = n.substring(rn.length()); 1215 if (!s.contains(".")) { 1216 if (ed.hasSliceName() && ed.getType().size() == 1) { 1217 typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode())); 1218 } else if (!ed.hasSliceName() && !s.equals("[x]")) { 1219 if (isDataType(s)) 1220 typeList.add(new TypeSlice(ed, s)); 1221 else if (isConstrainedDataType(s)) 1222 typeList.add(new TypeSlice(ed, baseType(s))); 1223 else if (isPrimitive(Utilities.uncapitalize(s))) 1224 typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s))); 1225 } else if (!ed.hasSliceName() && s.equals("[x]")) 1226 typeList.add(new TypeSlice(ed, null)); 1227 } 1228 } 1229 return true; 1230 } 1231 1232 1233 private List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, String path) { 1234 List<ElementRedirection> result = new ArrayList<ElementRedirection>(); 1235 result.addAll(redirector); 1236 result.add(new ElementRedirection(outcome, path)); 1237 return result; 1238 } 1239 1240 1241 private List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) { 1242 List<TypeRefComponent> res = new ArrayList<TypeRefComponent>(); 1243 for (TypeRefComponent tr : type) { 1244 if (t.equals(tr.getWorkingCode())) 1245 res.add(tr); 1246 } 1247 return res; 1248 } 1249 1250 1251 private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) { 1252 outcome.setContentReference(null); 1253 outcome.getType().clear(); // though it should be clear anyway 1254 outcome.getType().addAll(tgt.getType()); 1255 } 1256 1257 1258 private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) { 1259 if (cursor >= elements.size()) 1260 return false; 1261 String path = elements.get(cursor).getPath(); 1262 String prevPath = elements.get(cursor - 1).getPath(); 1263 return path.startsWith(prevPath + "."); 1264 } 1265 1266 1267 private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError { 1268 ElementDefinition res = profile.copy(); 1269 if (usage.hasSliceName()) 1270 res.setSliceName(usage.getSliceName()); 1271 if (usage.hasLabel()) 1272 res.setLabel(usage.getLabel()); 1273 for (Coding c : usage.getCode()) 1274 res.addCode(c); 1275 1276 if (usage.hasDefinition()) 1277 res.setDefinition(usage.getDefinition()); 1278 if (usage.hasShort()) 1279 res.setShort(usage.getShort()); 1280 if (usage.hasComment()) 1281 res.setComment(usage.getComment()); 1282 if (usage.hasRequirements()) 1283 res.setRequirements(usage.getRequirements()); 1284 for (StringType c : usage.getAlias()) 1285 res.addAlias(c.getValue()); 1286 if (usage.hasMin()) 1287 res.setMin(usage.getMin()); 1288 if (usage.hasMax()) 1289 res.setMax(usage.getMax()); 1290 1291 if (usage.hasFixed()) 1292 res.setFixed(usage.getFixed()); 1293 if (usage.hasPattern()) 1294 res.setPattern(usage.getPattern()); 1295 if (usage.hasExample()) 1296 res.setExample(usage.getExample()); 1297 if (usage.hasMinValue()) 1298 res.setMinValue(usage.getMinValue()); 1299 if (usage.hasMaxValue()) 1300 res.setMaxValue(usage.getMaxValue()); 1301 if (usage.hasMaxLength()) 1302 res.setMaxLength(usage.getMaxLength()); 1303 if (usage.hasMustSupport()) 1304 res.setMustSupport(usage.getMustSupport()); 1305 if (usage.hasBinding()) 1306 res.setBinding(usage.getBinding().copy()); 1307 for (ElementDefinitionConstraintComponent c : usage.getConstraint()) 1308 res.addConstraint(c); 1309 for (Extension e : usage.getExtension()) { 1310 if (!res.hasExtension(e.getUrl())) 1311 res.addExtension(e.copy()); 1312 } 1313 1314 return res; 1315 } 1316 1317 1318 private boolean checkExtensionDoco(ElementDefinition base) { 1319 // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff 1320 boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension"); 1321 if (isExtension) { 1322 base.setDefinition("An Extension"); 1323 base.setShort("Extension"); 1324 base.setCommentElement(null); 1325 base.setRequirementsElement(null); 1326 base.getAlias().clear(); 1327 base.getMapping().clear(); 1328 } 1329 return isExtension; 1330 } 1331 1332 1333 private String pathTail(List<ElementDefinition> diffMatches, int i) { 1334 1335 ElementDefinition d = diffMatches.get(i); 1336 String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath(); 1337 return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : ""); 1338 } 1339 1340 1341 private void markDerived(ElementDefinition outcome) { 1342 for (ElementDefinitionConstraintComponent inv : outcome.getConstraint()) 1343 inv.setUserData(IS_DERIVED, true); 1344 } 1345 1346 1347 private String summarizeSlicing(ElementDefinitionSlicingComponent slice) { 1348 StringBuilder b = new StringBuilder(); 1349 boolean first = true; 1350 for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) { 1351 if (first) 1352 first = false; 1353 else 1354 b.append(", "); 1355 b.append(d); 1356 } 1357 b.append("("); 1358 if (slice.hasOrdered()) 1359 b.append(slice.getOrderedElement().asStringValue()); 1360 b.append("/"); 1361 if (slice.hasRules()) 1362 b.append(slice.getRules().toCode()); 1363 b.append(")"); 1364 if (slice.hasDescription()) { 1365 b.append(" \""); 1366 b.append(slice.getDescription()); 1367 b.append("\""); 1368 } 1369 return b.toString(); 1370 } 1371 1372 1373 private void updateFromBase(ElementDefinition derived, ElementDefinition base) { 1374 if (base.hasBase()) { 1375 if (!derived.hasBase()) 1376 derived.setBase(new ElementDefinitionBaseComponent()); 1377 derived.getBase().setPath(base.getBase().getPath()); 1378 derived.getBase().setMin(base.getBase().getMin()); 1379 derived.getBase().setMax(base.getBase().getMax()); 1380 } else { 1381 if (!derived.hasBase()) 1382 derived.setBase(new ElementDefinitionBaseComponent()); 1383 derived.getBase().setPath(base.getPath()); 1384 derived.getBase().setMin(base.getMin()); 1385 derived.getBase().setMax(base.getMax()); 1386 } 1387 } 1388 1389 1390 private boolean pathStartsWith(String p1, String p2) { 1391 return p1.startsWith(p2); 1392 } 1393 1394 private boolean pathMatches(String p1, String p2) { 1395 return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains(".")); 1396 } 1397 1398 1399 private String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) { 1400 if (contextPath == null) 1401 return pathSimple; 1402// String ptail = pathSimple.substring(contextPath.length() + 1); 1403 if (redirector.size() > 0) { 1404 String ptail = pathSimple.substring(contextPath.length()+1); 1405 return redirector.get(redirector.size()-1).getPath()+"."+ptail; 1406// return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1); 1407 } else { 1408 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1409 return contextPath+"."+ptail; 1410 } 1411 } 1412 1413 private String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, String redirectSource) { 1414 String s; 1415 if (contextPath == null) 1416 s = pathSimple; 1417 else { 1418 if (redirector.size() > 0) { 1419 String ptail = pathSimple.substring(redirectSource.length() + 1); 1420 // ptail = ptail.substring(ptail.indexOf(".")+1); 1421 s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail; 1422 } else { 1423 String ptail = pathSimple.substring(pathSimple.indexOf(".")+1); 1424 s = contextPath+"."+ptail; 1425 } 1426 } 1427 return s; 1428 } 1429 1430 private StructureDefinition getProfileForDataType(TypeRefComponent type) { 1431 StructureDefinition sd = null; 1432 if (type.hasProfile()) { 1433 sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue()); 1434 if (sd == null) 1435 System.out.println("Failed to find referenced profile: " + type.getProfile()); 1436 } 1437 if (sd == null) 1438 sd = context.fetchTypeDefinition(type.getWorkingCode()); 1439 if (sd == null) 1440 System.out.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM 1441 return sd; 1442 } 1443 1444 private StructureDefinition getProfileForDataType(String type) { 1445 StructureDefinition sd = context.fetchTypeDefinition(type); 1446 if (sd == null) 1447 System.out.println("XX: failed to find profle for type: " + type); // debug GJM 1448 return sd; 1449 } 1450 1451 1452 public static String typeCode(List<TypeRefComponent> types) { 1453 StringBuilder b = new StringBuilder(); 1454 boolean first = true; 1455 for (TypeRefComponent type : types) { 1456 if (first) first = false; else b.append(", "); 1457 b.append(type.getWorkingCode()); 1458 if (type.hasTargetProfile()) 1459 b.append("{"+type.getTargetProfile()+"}"); 1460 else if (type.hasProfile()) 1461 b.append("{"+type.getProfile()+"}"); 1462 } 1463 return b.toString(); 1464 } 1465 1466 1467 private boolean isDataType(List<TypeRefComponent> types) { 1468 if (types.isEmpty()) 1469 return false; 1470 for (TypeRefComponent type : types) { 1471 String t = type.getWorkingCode(); 1472 if (!isDataType(t) && !isPrimitive(t)) 1473 return false; 1474 } 1475 return true; 1476 } 1477 1478 1479 /** 1480 * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url 1481 * @param url - the base url to use to turn internal references into absolute references 1482 * @param element - the Element to update 1483 * @return - the updated Element 1484 */ 1485 private ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) { 1486 if (element != null) { 1487 ElementDefinition defn = element; 1488 if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#")) 1489 defn.getBinding().setValueSet(url+defn.getBinding().getValueSet()); 1490 for (TypeRefComponent t : defn.getType()) { 1491 for (UriType u : t.getProfile()) { 1492 if (u.getValue().startsWith("#")) 1493 u.setValue(url+t.getProfile()); 1494 } 1495 for (UriType u : t.getTargetProfile()) { 1496 if (u.getValue().startsWith("#")) 1497 u.setValue(url+t.getTargetProfile()); 1498 } 1499 } 1500 if (webUrl != null) { 1501 // also, must touch up the markdown 1502 if (element.hasDefinition()) 1503 element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl)); 1504 if (element.hasComment()) 1505 element.setComment(processRelativeUrls(element.getComment(), webUrl)); 1506 if (element.hasRequirements()) 1507 element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl)); 1508 if (element.hasMeaningWhenMissing()) 1509 element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl)); 1510 } 1511 } 1512 return element; 1513 } 1514 1515 private String processRelativeUrls(String markdown, String webUrl) { 1516 StringBuilder b = new StringBuilder(); 1517 int i = 0; 1518 while (i < markdown.length()) { 1519 if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { 1520 int j = i + 2; 1521 while (j < markdown.length() && markdown.charAt(j) != ')') 1522 j++; 1523 if (j < markdown.length()) { 1524 String url = markdown.substring(i+2, j); 1525 if (!Utilities.isAbsoluteUrl(url)) { 1526 b.append("]("); 1527 b.append(webUrl); 1528 i = i + 1; 1529 } else 1530 b.append(markdown.charAt(i)); 1531 } else 1532 b.append(markdown.charAt(i)); 1533 } else { 1534 b.append(markdown.charAt(i)); 1535 } 1536 i++; 1537 } 1538 return b.toString(); 1539 } 1540 1541 1542 private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) { 1543 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1544 String path = current.getPath(); 1545 int cursor = list.indexOf(current)+1; 1546 while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) { 1547 if (pathMatches(list.get(cursor).getPath(), path)) 1548 result.add(list.get(cursor)); 1549 cursor++; 1550 } 1551 return result; 1552 } 1553 1554 private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) { 1555 if (src.hasOrderedElement()) 1556 dst.setOrderedElement(src.getOrderedElement().copy()); 1557 if (src.hasDiscriminator()) { 1558 // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll because it uses object equality, not string equality 1559 for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) { 1560 boolean found = false; 1561 for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) { 1562 if (matches(d, s)) { 1563 found = true; 1564 break; 1565 } 1566 } 1567 if (!found) 1568 dst.getDiscriminator().add(s); 1569 } 1570 } 1571 if (src.hasRulesElement()) 1572 dst.setRulesElement(src.getRulesElement().copy()); 1573 } 1574 1575 private boolean orderMatches(BooleanType diff, BooleanType base) { 1576 return (diff == null) || (base == null) || (diff.getValue() == base.getValue()); 1577 } 1578 1579 private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) { 1580 if (diff.isEmpty() || base.isEmpty()) 1581 return true; 1582 if (diff.size() != base.size()) 1583 return false; 1584 for (int i = 0; i < diff.size(); i++) 1585 if (!matches(diff.get(i), base.get(i))) 1586 return false; 1587 return true; 1588 } 1589 1590 private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) { 1591 return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath()); 1592 } 1593 1594 1595 private boolean ruleMatches(SlicingRules diff, SlicingRules base) { 1596 return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) || 1597 ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED)); 1598 } 1599 1600 private boolean isSlicedToOneOnly(ElementDefinition e) { 1601 return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); 1602 } 1603 1604 private ElementDefinitionSlicingComponent makeExtensionSlicing() { 1605 ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); 1606 slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); 1607 slice.setOrdered(false); 1608 slice.setRules(SlicingRules.OPEN); 1609 return slice; 1610 } 1611 1612 private boolean isExtension(ElementDefinition currentBase) { 1613 return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension"); 1614 } 1615 1616 private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException { 1617 end = Math.min(context.getElement().size(), end); 1618 start = Math.max(0, start); 1619 1620 for (int i = start; i <= end; i++) { 1621 String statedPath = context.getElement().get(i).getPath(); 1622 if (statedPath.startsWith(path+".")) { 1623 return true; 1624 } else if (!statedPath.endsWith(path)) 1625 break; 1626 } 1627 return false; 1628 } 1629 1630 private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException { 1631 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1632 for (int i = start; i <= end; i++) { 1633 String statedPath = context.getElement().get(i).getPath(); 1634 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(".")))) { 1635 /* 1636 * Commenting this out because it raises warnings when profiling inherited elements. For example, 1637 * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry') 1638 * Not sure we have enough information here to do the check properly. Might be better done when we're sorting the profile? 1639 1640 if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath())) 1641 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)); 1642 1643 */ 1644 result.add(context.getElement().get(i)); 1645 } 1646 } 1647 return result; 1648 } 1649 1650 private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) { 1651 int result = cursor; 1652 String path = context.getElement().get(cursor).getPath()+"."; 1653 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 1654 result++; 1655 return result; 1656 } 1657 1658 private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) { 1659 int result = cursor; 1660 String path = context.getElement().get(cursor).getPath()+"."; 1661 while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path)) 1662 result++; 1663 return result; 1664 } 1665 1666 private boolean unbounded(ElementDefinition definition) { 1667 StringType max = definition.getMaxElement(); 1668 if (max == null) 1669 return false; // this is not valid 1670 if (max.getValue().equals("1")) 1671 return false; 1672 if (max.getValue().equals("0")) 1673 return false; 1674 return true; 1675 } 1676 1677 private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD) throws DefinitionException, FHIRException { 1678 source.setUserData(GENERATED_IN_SNAPSHOT, dest); 1679 // we start with a clone of the base profile ('dest') and we copy from the profile ('source') 1680 // over the top for anything the source has 1681 ElementDefinition base = dest; 1682 ElementDefinition derived = source; 1683 derived.setUserData(DERIVATION_POINTER, base); 1684 boolean isExtension = checkExtensionDoco(base); 1685 1686 1687 // Before applying changes, apply them to what's in the profile 1688 // TODO: follow Chris's rules - Done by Lloyd 1689 StructureDefinition profile = null; 1690 if (base.hasSliceName()) 1691 profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue()) : null; 1692 if (profile==null) 1693 profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue()) : null; 1694 if (profile != null) { 1695 ElementDefinition e = profile.getSnapshot().getElement().get(0); 1696 base.setDefinition(e.getDefinition()); 1697 base.setShort(e.getShort()); 1698 if (e.hasCommentElement()) 1699 base.setCommentElement(e.getCommentElement()); 1700 if (e.hasRequirementsElement()) 1701 base.setRequirementsElement(e.getRequirementsElement()); 1702 base.getAlias().clear(); 1703 base.getAlias().addAll(e.getAlias()); 1704 base.getMapping().clear(); 1705 base.getMapping().addAll(e.getMapping()); 1706 } 1707 if (derived != null) { 1708 if (derived.hasSliceName()) { 1709 base.setSliceName(derived.getSliceName()); 1710 } 1711 1712 if (derived.hasShortElement()) { 1713 if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false)) 1714 base.setShortElement(derived.getShortElement().copy()); 1715 else if (trimDifferential) 1716 derived.setShortElement(null); 1717 else if (derived.hasShortElement()) 1718 derived.getShortElement().setUserData(DERIVATION_EQUALS, true); 1719 } 1720 1721 if (derived.hasDefinitionElement()) { 1722 if (derived.getDefinition().startsWith("...")) 1723 base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition())); 1724 else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) 1725 base.setDefinitionElement(derived.getDefinitionElement().copy()); 1726 else if (trimDifferential) 1727 derived.setDefinitionElement(null); 1728 else if (derived.hasDefinitionElement()) 1729 derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true); 1730 } 1731 1732 if (derived.hasCommentElement()) { 1733 if (derived.getComment().startsWith("...")) 1734 base.setComment(Utilities.appendDerivedTextToBase(base.getComment(), derived.getComment())); 1735 else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false)) 1736 base.setCommentElement(derived.getCommentElement().copy()); 1737 else if (trimDifferential) 1738 base.setCommentElement(derived.getCommentElement().copy()); 1739 else if (derived.hasCommentElement()) 1740 derived.getCommentElement().setUserData(DERIVATION_EQUALS, true); 1741 } 1742 1743 if (derived.hasLabelElement()) { 1744 if (derived.getLabel().startsWith("...")) 1745 base.setLabel(Utilities.appendDerivedTextToBase(base.getLabel(), derived.getLabel())); 1746 else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false)) 1747 base.setLabelElement(derived.getLabelElement().copy()); 1748 else if (trimDifferential) 1749 base.setLabelElement(derived.getLabelElement().copy()); 1750 else if (derived.hasLabelElement()) 1751 derived.getLabelElement().setUserData(DERIVATION_EQUALS, true); 1752 } 1753 1754 if (derived.hasRequirementsElement()) { 1755 if (derived.getRequirements().startsWith("...")) 1756 base.setRequirements(Utilities.appendDerivedTextToBase(base.getRequirements(), derived.getRequirements())); 1757 else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false)) 1758 base.setRequirementsElement(derived.getRequirementsElement().copy()); 1759 else if (trimDifferential) 1760 base.setRequirementsElement(derived.getRequirementsElement().copy()); 1761 else if (derived.hasRequirementsElement()) 1762 derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true); 1763 } 1764 // sdf-9 1765 if (derived.hasRequirements() && !base.getPath().contains(".")) 1766 derived.setRequirements(null); 1767 if (base.hasRequirements() && !base.getPath().contains(".")) 1768 base.setRequirements(null); 1769 1770 if (derived.hasAlias()) { 1771 if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false)) 1772 for (StringType s : derived.getAlias()) { 1773 if (!base.hasAlias(s.getValue())) 1774 base.getAlias().add(s.copy()); 1775 } 1776 else if (trimDifferential) 1777 derived.getAlias().clear(); 1778 else 1779 for (StringType t : derived.getAlias()) 1780 t.setUserData(DERIVATION_EQUALS, true); 1781 } 1782 1783 if (derived.hasMinElement()) { 1784 if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { 1785 if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply 1786 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", ValidationMessage.IssueSeverity.ERROR)); 1787 base.setMinElement(derived.getMinElement().copy()); 1788 } else if (trimDifferential) 1789 derived.setMinElement(null); 1790 else 1791 derived.getMinElement().setUserData(DERIVATION_EQUALS, true); 1792 } 1793 1794 if (derived.hasMaxElement()) { 1795 if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { 1796 if (isLargerMax(derived.getMax(), base.getMax())) 1797 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR)); 1798 base.setMaxElement(derived.getMaxElement().copy()); 1799 } else if (trimDifferential) 1800 derived.setMaxElement(null); 1801 else 1802 derived.getMaxElement().setUserData(DERIVATION_EQUALS, true); 1803 } 1804 1805 if (derived.hasFixed()) { 1806 if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) { 1807 base.setFixed(derived.getFixed().copy()); 1808 } else if (trimDifferential) 1809 derived.setFixed(null); 1810 else 1811 derived.getFixed().setUserData(DERIVATION_EQUALS, true); 1812 } 1813 1814 if (derived.hasPattern()) { 1815 if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) { 1816 base.setPattern(derived.getPattern().copy()); 1817 } else 1818 if (trimDifferential) 1819 derived.setPattern(null); 1820 else 1821 derived.getPattern().setUserData(DERIVATION_EQUALS, true); 1822 } 1823 1824 for (ElementDefinitionExampleComponent ex : derived.getExample()) { 1825 boolean found = false; 1826 for (ElementDefinitionExampleComponent exS : base.getExample()) 1827 if (Base.compareDeep(ex, exS, false)) 1828 found = true; 1829 if (!found) 1830 base.addExample(ex.copy()); 1831 else if (trimDifferential) 1832 derived.getExample().remove(ex); 1833 else 1834 ex.setUserData(DERIVATION_EQUALS, true); 1835 } 1836 1837 if (derived.hasMaxLengthElement()) { 1838 if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false)) 1839 base.setMaxLengthElement(derived.getMaxLengthElement().copy()); 1840 else if (trimDifferential) 1841 derived.setMaxLengthElement(null); 1842 else 1843 derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true); 1844 } 1845 1846 if (derived.hasMaxValue()) { 1847 if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false)) 1848 base.setMaxValue(derived.getMaxValue().copy()); 1849 else if (trimDifferential) 1850 derived.setMaxValue(null); 1851 else 1852 derived.getMaxValue().setUserData(DERIVATION_EQUALS, true); 1853 } 1854 1855 if (derived.hasMinValue()) { 1856 if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false)) 1857 base.setMinValue(derived.getMinValue().copy()); 1858 else if (trimDifferential) 1859 derived.setMinValue(null); 1860 else 1861 derived.getMinValue().setUserData(DERIVATION_EQUALS, true); 1862 } 1863 1864 // todo: what to do about conditions? 1865 // condition : id 0..* 1866 1867 if (derived.hasMustSupportElement()) { 1868 if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))) 1869 base.setMustSupportElement(derived.getMustSupportElement().copy()); 1870 else if (trimDifferential) 1871 derived.setMustSupportElement(null); 1872 else 1873 derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true); 1874 } 1875 1876 1877 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 1878 // but extensions can change isModifier 1879 if (isExtension) { 1880 if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) 1881 base.setIsModifierElement(derived.getIsModifierElement().copy()); 1882 else if (trimDifferential) 1883 derived.setIsModifierElement(null); 1884 else if (derived.hasIsModifierElement()) 1885 derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true); 1886 if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) 1887 base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy()); 1888 else if (trimDifferential) 1889 derived.setIsModifierReasonElement(null); 1890 else if (derived.hasIsModifierReasonElement()) 1891 derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true); 1892 } 1893 1894 if (derived.hasBinding()) { 1895 if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 1896 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 1897 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)); 1898// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 1899 else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) { 1900 ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet()); 1901 ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet()); 1902 if (baseVs == null) { 1903 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 1904 } else if (contextVs == null) { 1905 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 1906 } else { 1907 ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false); 1908 ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false); 1909 if (expBase.getValueset() == null) 1910 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 1911 else if (expDerived.getValueset() == null) 1912 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 1913 else if (!isSubset(expBase.getValueset(), expDerived.getValueset())) 1914 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR)); 1915 1916 } 1917 } 1918 base.setBinding(derived.getBinding().copy()); 1919 } else if (trimDifferential) 1920 derived.setBinding(null); 1921 else 1922 derived.getBinding().setUserData(DERIVATION_EQUALS, true); 1923 } // else if (base.hasBinding() && doesn't have bindable type ) 1924 // base 1925 1926 if (derived.hasIsSummaryElement()) { 1927 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) { 1928 if (base.hasIsSummary()) 1929 throw new Error("Error in profile "+pn+" at "+derived.getPath()+": Base isSummary = "+base.getIsSummaryElement().asStringValue()+", derived isSummary = "+derived.getIsSummaryElement().asStringValue()); 1930 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 1931 } else if (trimDifferential) 1932 derived.setIsSummaryElement(null); 1933 else 1934 derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true); 1935 } 1936 1937 if (derived.hasType()) { 1938 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 1939 if (base.hasType()) { 1940 for (TypeRefComponent ts : derived.getType()) { 1941// if (!ts.hasCode()) { // ommitted in the differential; copy it over.... 1942// if (base.getType().size() > 1) 1943// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": constrained type code must be present if there are multiple types ("+base.typeSummary()+")"); 1944// if (base.getType().get(0).getCode() != null) 1945// ts.setCode(base.getType().get(0).getCode()); 1946// } 1947 boolean ok = false; 1948 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1949 String t = ts.getWorkingCode(); 1950 for (TypeRefComponent td : base.getType()) {; 1951 String tt = td.getWorkingCode(); 1952 b.append(tt); 1953 if (td.hasCode() && (tt.equals(t) || "Extension".equals(tt) || (t.equals("uri") && tt.equals("string")) || // work around for old badly generated SDs 1954 "Element".equals(tt) || "*".equals(tt) || 1955 (("Resource".equals(tt) || ("DomainResource".equals(tt)) && pkp.isResource(t))))) 1956 ok = true; 1957 } 1958 if (!ok) 1959 throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+t+" from "+b.toString()+" in "+srcSD.getUrl()); 1960 } 1961 } 1962 base.getType().clear(); 1963 for (TypeRefComponent t : derived.getType()) { 1964 TypeRefComponent tt = t.copy(); 1965// tt.setUserData(DERIVATION_EQUALS, true); 1966 base.getType().add(tt); 1967 } 1968 } 1969 else if (trimDifferential) 1970 derived.getType().clear(); 1971 else 1972 for (TypeRefComponent t : derived.getType()) 1973 t.setUserData(DERIVATION_EQUALS, true); 1974 } 1975 1976 if (derived.hasMapping()) { 1977 // todo: mappings are not cumulative - one replaces another 1978 if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) { 1979 for (ElementDefinitionMappingComponent s : derived.getMapping()) { 1980 boolean found = false; 1981 for (ElementDefinitionMappingComponent d : base.getMapping()) { 1982 found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap())); 1983 } 1984 if (!found) 1985 base.getMapping().add(s); 1986 } 1987 } 1988 else if (trimDifferential) 1989 derived.getMapping().clear(); 1990 else 1991 for (ElementDefinitionMappingComponent t : derived.getMapping()) 1992 t.setUserData(DERIVATION_EQUALS, true); 1993 } 1994 1995 // todo: constraints are cumulative. there is no replacing 1996 for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 1997 s.setUserData(IS_DERIVED, true); 1998 if (!s.hasSource()) 1999 s.setSource(base.getId()); 2000 } 2001 if (derived.hasConstraint()) { 2002 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 2003 ElementDefinitionConstraintComponent inv = s.copy(); 2004 base.getConstraint().add(inv); 2005 } 2006 } 2007 2008 // now, check that we still have a bindable type; if not, delete the binding - see task 8477 2009 if (dest.hasBinding() && !hasBindableType(dest)) 2010 dest.setBinding(null); 2011 2012 // finally, we copy any extensions from source to dest 2013 for (Extension ex : derived.getExtension()) { 2014 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ex.getUrl()); 2015 if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) 2016 ToolingExtensions.removeExtension(dest, ex.getUrl()); 2017 dest.addExtension(ex.copy()); 2018 } 2019 } 2020 } 2021 2022 private boolean hasBindableType(ElementDefinition ed) { 2023 for (TypeRefComponent tr : ed.getType()) { 2024 if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code")) 2025 return true; 2026 } 2027 return false; 2028 } 2029 2030 2031 private boolean isLargerMax(String derived, String base) { 2032 if ("*".equals(base)) 2033 return false; 2034 if ("*".equals(derived)) 2035 return true; 2036 return Integer.parseInt(derived) > Integer.parseInt(base); 2037 } 2038 2039 2040 private boolean isSubset(ValueSet expBase, ValueSet expDerived) { 2041 return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion()); 2042 } 2043 2044 2045 private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) { 2046 for (ValueSetExpansionContainsComponent cc : contains) { 2047 if (!inExpansion(cc, expansion.getContains())) 2048 return false; 2049 if (!codesInExpansion(cc.getContains(), expansion)) 2050 return false; 2051 } 2052 return true; 2053 } 2054 2055 2056 private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) { 2057 for (ValueSetExpansionContainsComponent cc1 : contains) { 2058 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) 2059 return true; 2060 if (inExpansion(cc, cc1.getContains())) 2061 return true; 2062 } 2063 return false; 2064 } 2065 2066 public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { 2067 for (ElementDefinition edb : base.getSnapshot().getElement()) { 2068 if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { 2069 ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); 2070 if (edm == null) { 2071 ElementDefinition edd = derived.getDifferential().addElement(); 2072 edd.setPath(edb.getPath()); 2073 edd.setMax("0"); 2074 } else if (edb.hasSlicing()) { 2075 closeChildren(base, edb, derived, edm); 2076 } 2077 } 2078 } 2079 sortDifferential(base, derived, derived.getName(), new ArrayList<String>()); 2080 } 2081 2082 private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) { 2083 String path = edb.getPath()+"."; 2084 int baseStart = base.getSnapshot().getElement().indexOf(edb); 2085 int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1); 2086 int diffStart = derived.getDifferential().getElement().indexOf(edm); 2087 int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1); 2088 2089 for (int cBase = baseStart; cBase < baseEnd; cBase++) { 2090 ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); 2091 if (isImmediateChild(edBase, edb)) { 2092 ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd); 2093 if (edMatch == null) { 2094 ElementDefinition edd = derived.getDifferential().addElement(); 2095 edd.setPath(edBase.getPath()); 2096 edd.setMax("0"); 2097 } else { 2098 closeChildren(base, edBase, derived, edMatch); 2099 } 2100 } 2101 } 2102 } 2103 2104 2105 2106 2107 private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) { 2108 String path = ed.getPath()+"."; 2109 while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) 2110 cursor++; 2111 return cursor; 2112 } 2113 2114 2115 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) { 2116 for (ElementDefinition t : list) 2117 if (t.getPath().equals(ed.getPath())) 2118 return t; 2119 return null; 2120 } 2121 2122 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) { 2123 for (int i = start; i < end; i++) { 2124 ElementDefinition t = list.get(i); 2125 if (t.getPath().equals(ed.getPath())) 2126 return t; 2127 } 2128 return null; 2129 } 2130 2131 2132 private boolean isImmediateChild(ElementDefinition ed) { 2133 String p = ed.getPath(); 2134 if (!p.contains(".")) 2135 return false; 2136 p = p.substring(p.indexOf(".")+1); 2137 return !p.contains("."); 2138 } 2139 2140 private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { 2141 String p = candidate.getPath(); 2142 if (!p.contains(".")) 2143 return false; 2144 if (!p.startsWith(base.getPath()+".")) 2145 return false; 2146 p = p.substring(base.getPath().length()+1); 2147 return !p.contains("."); 2148 } 2149 2150 public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException { 2151 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 2152 gen.setTranslator(getTranslator()); 2153 TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false, TableGenerationMode.XML); 2154 2155 boolean deep = false; 2156 String m = ""; 2157 boolean vdeep = false; 2158 if (ed.getSnapshot().getElementFirstRep().getIsModifier()) 2159 m = "modifier_"; 2160 for (ElementDefinition eld : ed.getSnapshot().getElement()) { 2161 deep = deep || eld.getPath().contains("Extension.extension."); 2162 vdeep = vdeep || eld.getPath().contains("Extension.extension.extension."); 2163 } 2164 Row r = gen.new Row(); 2165 model.getRows().add(r); 2166 String en; 2167 if (!full) 2168 en = ed.getName(); 2169 else if (ed.getSnapshot().getElement().get(0).getIsModifier()) 2170 en = "modifierExtension"; 2171 else 2172 en = "extension"; 2173 2174 r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null)); 2175 r.getCells().add(gen.new Cell()); 2176 r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null)); 2177 2178 ElementDefinition ved = null; 2179 if (full || vdeep) { 2180 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 2181 2182 r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2183 List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0)); 2184 for (ElementDefinition child : children) 2185 if (!child.getPath().endsWith(".id")) 2186 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, null); 2187 } else if (deep) { 2188 List<ElementDefinition> children = new ArrayList<ElementDefinition>(); 2189 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 2190 if (ted.getPath().equals("Extension.extension")) 2191 children.add(ted); 2192 } 2193 2194 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 2195 r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2196 2197 for (ElementDefinition c : children) { 2198 ved = getValueFor(ed, c); 2199 ElementDefinition ued = getUrlFor(ed, c); 2200 if (ved != null && ued != null) { 2201 Row r1 = gen.new Row(); 2202 r.getSubRows().add(r1); 2203 r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null)); 2204 r1.getCells().add(gen.new Cell()); 2205 r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null)); 2206 genTypes(gen, r1, ved, defFile, ed, corePath, imagePath); 2207 Cell cell = gen.new Cell(); 2208 cell.addMarkdown(c.getDefinition()); 2209 r1.getCells().add(cell); 2210 r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2211 } 2212 } 2213 } else { 2214 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 2215 if (ted.getPath().startsWith("Extension.value")) 2216 ved = ted; 2217 } 2218 2219 genTypes(gen, r, ved, defFile, ed, corePath, imagePath); 2220 2221 r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2222 } 2223 Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null); 2224 Piece cc = gen.new Piece(null, ed.getName()+": ", null); 2225 c.addPiece(gen.new Piece("br")).addPiece(cc); 2226 c.addMarkdown(ed.getDescription()); 2227 2228 if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) { 2229 c.addPiece(gen.new Piece("br")); 2230 BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath()); 2231 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"))); 2232 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))); 2233 if (ved.getBinding().hasStrength()) { 2234 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null))); 2235 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition()))); 2236 c.getPieces().add(gen.new Piece(null, ")", null)); 2237 } 2238 } 2239 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null)); 2240 r.getCells().add(c); 2241 2242 try { 2243 return gen.generate(model, corePath, 0, outputTracker); 2244 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2245 throw new FHIRException(e.getMessage(), e); 2246 } 2247 } 2248 2249 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 2250 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 2251 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 2252 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url")) 2253 return ed.getSnapshot().getElement().get(i); 2254 i++; 2255 } 2256 return null; 2257 } 2258 2259 private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) { 2260 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 2261 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 2262 if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value")) 2263 return ed.getSnapshot().getElement().get(i); 2264 i++; 2265 } 2266 return null; 2267 } 2268 2269 2270 private static final int AGG_NONE = 0; 2271 private static final int AGG_IND = 1; 2272 private static final int AGG_GR = 2; 2273 private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false; 2274 2275 private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath) { 2276 Cell c = gen.new Cell(); 2277 r.getCells().add(c); 2278 List<TypeRefComponent> types = e.getType(); 2279 if (!e.hasType()) { 2280 if (e.hasContentReference()) { 2281 return c; 2282 } else { 2283 ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER); 2284 if (d != null && d.hasType()) { 2285 types = new ArrayList<ElementDefinition.TypeRefComponent>(); 2286 for (TypeRefComponent tr : d.getType()) { 2287 TypeRefComponent tt = tr.copy(); 2288 tt.setUserData(DERIVATION_EQUALS, true); 2289 types.add(tt); 2290 } 2291 } else 2292 return c; 2293 } 2294 } 2295 2296 boolean first = true; 2297 2298 TypeRefComponent tl = null; 2299 for (TypeRefComponent t : types) { 2300 if (first) 2301 first = false; 2302 else 2303 c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null))); 2304 tl = t; 2305 if (t.hasTarget()) { 2306 c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null)); 2307 c.getPieces().add(gen.new Piece(null, "(", null)); 2308 boolean tfirst = true; 2309 for (UriType u : t.getTargetProfile()) { 2310 if (tfirst) 2311 tfirst = false; 2312 else 2313 c.addPiece(gen.new Piece(null, " | ", null)); 2314 genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue()); 2315 } 2316 c.getPieces().add(gen.new Piece(null, ")", null)); 2317 if (t.getAggregation().size() > 0) { 2318 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null)); 2319 boolean firstA = true; 2320 for (Enumeration<AggregationMode> a : t.getAggregation()) { 2321 if (firstA = true) 2322 firstA = false; 2323 else 2324 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null)); 2325 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue()))); 2326 } 2327 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null)); 2328 } 2329 } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type 2330 String ref; 2331 ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue()); 2332 if (ref != null) { 2333 String[] parts = ref.split("\\|"); 2334 if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) { 2335// c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd 2336 c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode()))); 2337 } else { 2338// c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode()))); 2339 c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getWorkingCode()))); 2340 } 2341 } else 2342 c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null))); 2343 } else { 2344 String tc = t.getWorkingCode(); 2345 if (pkp != null && pkp.hasLinkFor(tc)) { 2346 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null))); 2347 } else 2348 c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null))); 2349 } 2350 } 2351 return c; 2352 } 2353 2354 2355 public void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, TypeRefComponent t, String u) { 2356 if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2357 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u); 2358 if (sd != null) { 2359 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 2360 c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null))); 2361 } else { 2362 String rn = u.substring(40); 2363 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null))); 2364 } 2365 } else if (Utilities.isAbsoluteUrl(u)) { 2366 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u); 2367 if (sd != null) { 2368 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 2369 String ref = pkp.getLinkForProfile(null, sd.getUrl()); 2370 if (ref.contains("|")) 2371 ref = ref.substring(0, ref.indexOf("|")); 2372 c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null))); 2373 } else 2374 c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null))); 2375 } else if (t.hasTargetProfile() && u.startsWith("#")) 2376 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.substring(1).toLowerCase()+".html", u, null))); 2377 } 2378 2379 private boolean isProfiledType(List<CanonicalType> theProfile) { 2380 for (CanonicalType next : theProfile){ 2381 if (StringUtils.defaultString(next.getValueAsString()).contains(":")) { 2382 return true; 2383 } 2384 } 2385 return false; 2386 } 2387 2388 2389 private String codeForAggregation(AggregationMode a) { 2390 switch (a) { 2391 case BUNDLED : return "b"; 2392 case CONTAINED : return "c"; 2393 case REFERENCED: return "r"; 2394 default: return "?"; 2395 } 2396 } 2397 2398 private String hintForAggregation(AggregationMode a) { 2399 if (a != null) 2400 return a.getDefinition(); 2401 else 2402 return null; 2403 } 2404 2405 2406 private String checkPrepend(String corePath, String path) { 2407 if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:"))) 2408 return corePath+path; 2409 else 2410 return path; 2411 } 2412 2413 2414 private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) { 2415 for (ElementDefinition ed : elements) 2416 if (ed.hasSliceName() && ("#"+ed.getSliceName()).equals(contentReference)) 2417 return ed; 2418 return null; 2419 } 2420 2421 private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) { 2422 for (ElementDefinition ed : elements) 2423 if (ed.hasId() && ("#"+ed.getId()).equals(contentReference)) 2424 return ed; 2425 return null; 2426 } 2427 2428 2429 public static String describeExtensionContext(StructureDefinition ext) { 2430 StringBuilder b = new StringBuilder(); 2431 b.append("Use on "); 2432 for (int i = 0; i < ext.getContext().size(); i++) { 2433 StructureDefinitionContextComponent ec = ext.getContext().get(i); 2434 if (i > 0) 2435 b.append(i < ext.getContext().size() - 1 ? ", " : " or "); 2436 b.append(ec.getType().getDisplay()); 2437 b.append(" "); 2438 b.append(ec.getExpression()); 2439 } 2440 if (ext.hasContextInvariant()) { 2441 b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = "); 2442 boolean first = true; 2443 for (StringType s : ext.getContextInvariant()) { 2444 if (first) 2445 first = false; 2446 else 2447 b.append(", "); 2448 b.append("<code>"+s.getValue()+"</code>"); 2449 } 2450 } 2451 return b.toString(); 2452 } 2453 2454 private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) { 2455 IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 2456 StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 2457 if (min.isEmpty() && fallback != null) 2458 min = fallback.getMinElement(); 2459 if (max.isEmpty() && fallback != null) 2460 max = fallback.getMaxElement(); 2461 2462 tracker.used = !max.isEmpty() && !max.getValue().equals("0"); 2463 2464 if (min.isEmpty() && max.isEmpty()) 2465 return null; 2466 else 2467 return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue()); 2468 } 2469 2470 private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) { 2471 IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 2472 StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 2473 if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 2474 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 2475 if (base.hasMinElement()) { 2476 min = base.getMinElement().copy(); 2477 min.setUserData(DERIVATION_EQUALS, true); 2478 } 2479 } 2480 if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 2481 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 2482 if (base.hasMaxElement()) { 2483 max = base.getMaxElement().copy(); 2484 max.setUserData(DERIVATION_EQUALS, true); 2485 } 2486 } 2487 if (min.isEmpty() && fallback != null) 2488 min = fallback.getMinElement(); 2489 if (max.isEmpty() && fallback != null) 2490 max = fallback.getMaxElement(); 2491 2492 if (!max.isEmpty()) 2493 tracker.used = !max.getValue().equals("0"); 2494 2495 Cell cell = gen.new Cell(null, null, null, null, null); 2496 row.getCells().add(cell); 2497 if (!min.isEmpty() || !max.isEmpty()) { 2498 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null))); 2499 cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null))); 2500 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null))); 2501 } 2502 } 2503 2504 2505 private Piece checkForNoChange(Element source, Piece piece) { 2506 if (source.hasUserData(DERIVATION_EQUALS)) { 2507 piece.addStyle("opacity: 0.4"); 2508 } 2509 return piece; 2510 } 2511 2512 private Piece checkForNoChange(Element src1, Element src2, Piece piece) { 2513 if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) { 2514 piece.addStyle("opacity: 0.5"); 2515 } 2516 return piece; 2517 } 2518 2519 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 { 2520 assert(diff != snapshot);// check it's ok to get rid of one of these 2521 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 2522 gen.setTranslator(getTranslator()); 2523 TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), false, TableGenerationMode.XML); 2524 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 2525 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2526 profiles.add(profile); 2527 if (list.isEmpty()) { 2528 ElementDefinition root = new ElementDefinition().setPath(profile.getType()); 2529 root.setId(profile.getType()); 2530 list.add(root); 2531 } 2532 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, null); 2533 try { 2534 return gen.generate(model, imagePath, 0, outputTracker); 2535 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2536 throw new FHIRException("Error generating table for profile " + profile.getUrl() + ": " + e.getMessage(), e); 2537 } 2538 } 2539 2540 2541 public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException { 2542 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 2543 gen.setTranslator(getTranslator()); 2544 TableModel model = gen.initGridTable(corePath, profile.getId()); 2545 List<ElementDefinition> list = profile.getSnapshot().getElement(); 2546 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2547 profiles.add(profile); 2548 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)); 2549 try { 2550 return gen.generate(model, imagePath, 1, outputTracker); 2551 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2552 throw new FHIRException(e.getMessage(), e); 2553 } 2554 } 2555 2556 2557 private boolean usesMustSupport(List<ElementDefinition> list) { 2558 for (ElementDefinition ed : list) 2559 if (ed.hasMustSupport() && ed.getMustSupport()) 2560 return true; 2561 return false; 2562 } 2563 2564 2565 private Row 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, Row slicingRow) throws IOException, FHIRException { 2566 Row originalRow = slicingRow; 2567 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 2568 String s = tail(element.getPath()); 2569 if (element.hasSliceName()) 2570 s = s +":"+element.getSliceName(); 2571 Row typesRow = null; 2572 2573 List<ElementDefinition> children = getChildren(all, element); 2574 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 2575// if (!snapshot && isExtension && extensions != null && extensions != isExtension) 2576// return; 2577 2578 if (!onlyInformationIsMapping(all, element)) { 2579 Row row = gen.new Row(); 2580 row.setAnchor(element.getPath()); 2581 row.setColor(getRowColor(element, isConstraintMode)); 2582 if (element.hasSlicing()) 2583 row.setLineColor(1); 2584 else if (element.hasSliceName()) 2585 row.setLineColor(2); 2586 else 2587 row.setLineColor(0); 2588 boolean hasDef = element != null; 2589 boolean ext = false; 2590 if (tail(element.getPath()).equals("extension")) { 2591 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 2592 row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2593 else 2594 row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2595 ext = true; 2596 } else if (tail(element.getPath()).equals("modifierExtension")) { 2597 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 2598 row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2599 else 2600 row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2601 } else if (!hasDef || element.getType().size() == 0) 2602 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 2603 else if (hasDef && element.getType().size() > 1) { 2604 if (allAreReference(element.getType())) 2605 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2606 else { 2607 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 2608 typesRow = row; 2609 } 2610 } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@")) 2611 row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE); 2612 else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode())) 2613 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 2614 else if (hasDef && element.getType().get(0).hasTarget()) 2615 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2616 else if (hasDef && isDataType(element.getType().get(0).getWorkingCode())) 2617 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2618 else 2619 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 2620 String ref = defPath == null ? null : defPath + element.getId(); 2621 UnusedTracker used = new UnusedTracker(); 2622 used.used = true; 2623 if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR)) 2624 s = "@"+s; 2625 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); 2626 row.getCells().add(left); 2627 Cell gc = gen.new Cell(); 2628 row.getCells().add(gc); 2629 if (element != null && element.getIsModifier()) 2630 checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false)); 2631 if (element != null && element.getMustSupport()) 2632 checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false)); 2633 if (element != null && element.getIsSummary()) 2634 checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false)); 2635 if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty())) 2636 gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null, null, false); 2637 2638 ExtensionContext extDefn = null; 2639 if (ext) { 2640 if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) { 2641 String eurl = element.getType().get(0).getProfile().get(0).getValue(); 2642 extDefn = locateExtension(StructureDefinition.class, eurl); 2643 if (extDefn == null) { 2644 genCardinality(gen, element, row, hasDef, used, null); 2645 row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null)); 2646 generateDescription(gen, row, element, null, used.used, profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot); 2647 } else { 2648 String name = urltail(eurl); 2649 left.getPieces().get(0).setText(name); 2650 // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename")); 2651 left.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl()); 2652 genCardinality(gen, element, row, hasDef, used, extDefn.getElement()); 2653 ElementDefinition valueDefn = extDefn.getExtensionValueDefinition(); 2654 if (valueDefn != null && !"0".equals(valueDefn.getMax())) 2655 genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath); 2656 else // if it's complex, we just call it nothing 2657 // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile); 2658 row.getCells().add(gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null)); 2659 generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot); 2660 } 2661 } else { 2662 genCardinality(gen, element, row, hasDef, used, null); 2663 if ("0".equals(element.getMax())) 2664 row.getCells().add(gen.new Cell()); 2665 else 2666 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2667 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot); 2668 } 2669 } else { 2670 genCardinality(gen, element, row, hasDef, used, null); 2671 if (element.hasSlicing()) 2672 row.getCells().add(gen.new Cell(null, corePath+"profiling.html#slicing", "(Slice Definition)", null, null)); 2673 else if (hasDef && !"0".equals(element.getMax()) && typesRow == null) 2674 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2675 else 2676 row.getCells().add(gen.new Cell()); 2677 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot); 2678 } 2679 if (element.hasSlicing()) { 2680 if (standardExtensionSlicing(element)) { 2681 used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing ... element.hasType() && element.getType().get(0).hasProfile(); 2682 showMissing = false; //? 2683 } else { 2684 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 2685 slicingRow = row; 2686 for (Cell cell : row.getCells()) 2687 for (Piece p : cell.getPieces()) { 2688 p.addStyle("font-style: italic"); 2689 } 2690 } 2691 } else if (element.hasSliceName()) { 2692 row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM); 2693 } 2694 if (used.used || showMissing) 2695 rows.add(row); 2696 if (!used.used && !element.hasSlicing()) { 2697 for (Cell cell : row.getCells()) 2698 for (Piece p : cell.getPieces()) { 2699 p.setStyle("text-decoration:line-through"); 2700 p.setReference(null); 2701 } 2702 } else{ 2703 if (slicingRow != originalRow && !children.isEmpty()) { 2704 // we've entered a slice; we're going to create a holder row for the slice children 2705 Row hrow = gen.new Row(); 2706 hrow.setAnchor(element.getPath()); 2707 hrow.setColor(getRowColor(element, isConstraintMode)); 2708 hrow.setLineColor(1); 2709 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 2710 hrow.getCells().add(gen.new Cell(null, null, "(All Slices)", "", null)); 2711 hrow.getCells().add(gen.new Cell()); 2712 hrow.getCells().add(gen.new Cell()); 2713 hrow.getCells().add(gen.new Cell()); 2714 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null)); 2715 row.getSubRows().add(hrow); 2716 row = hrow; 2717 } 2718 if (typesRow != null && !children.isEmpty()) { 2719 // we've entered a typing slice; we're going to create a holder row for the all types children 2720 Row hrow = gen.new Row(); 2721 hrow.setAnchor(element.getPath()); 2722 hrow.setColor(getRowColor(element, isConstraintMode)); 2723 hrow.setLineColor(1); 2724 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 2725 hrow.getCells().add(gen.new Cell(null, null, "(All Types)", "", null)); 2726 hrow.getCells().add(gen.new Cell()); 2727 hrow.getCells().add(gen.new Cell()); 2728 hrow.getCells().add(gen.new Cell()); 2729 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null)); 2730 row.getSubRows().add(hrow); 2731 row = hrow; 2732 } 2733 2734 Row currRow = row; 2735 for (ElementDefinition child : children) { 2736 if (!child.hasSliceName()) 2737 currRow = row; 2738 if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) 2739 currRow = genElement(defPath, gen, currRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow); 2740 } 2741// if (!snapshot && (extensions == null || !extensions)) 2742// for (ElementDefinition child : children) 2743// if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension")) 2744// genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants); 2745 } 2746 if (typesRow != null) { 2747 makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName); 2748 } 2749 } 2750 return slicingRow; 2751 } 2752 2753 private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, String corePath, String profileBaseFileName) { 2754 // create a child for each choice 2755 for (TypeRefComponent tr : element.getType()) { 2756 Row choicerow = gen.new Row(); 2757 String t = tr.getWorkingCode(); 2758 if (isReference(t)) { 2759 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null)); 2760 choicerow.getCells().add(gen.new Cell()); 2761 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2762 choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2763 Cell c = gen.new Cell(); 2764 choicerow.getCells().add(c); 2765 if (ADD_REFERENCE_TO_TABLE) { 2766 if (tr.getWorkingCode().equals("canonical")) 2767 c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null)); 2768 else 2769 c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null)); 2770 c.getPieces().add(gen.new Piece(null, "(", null)); 2771 } 2772 boolean first = true; 2773 for (CanonicalType rt : tr.getTargetProfile()) { 2774 if (!first) 2775 c.getPieces().add(gen.new Piece(null, " | ", null)); 2776 genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue()); 2777 first = false; 2778 } 2779 if (ADD_REFERENCE_TO_TABLE) 2780 c.getPieces().add(gen.new Piece(null, ")", null)); 2781 2782 } else { 2783 StructureDefinition sd = context.fetchTypeDefinition(t); 2784 if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 2785 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 2786 choicerow.getCells().add(gen.new Cell()); 2787 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2788 choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 2789 choicerow.getCells().add(gen.new Cell(null, corePath+"datatypes.html#"+t, t, null, null)); 2790 // } else if (definitions.getConstraints().contthnsKey(t)) { 2791 // ProfiledType pt = definitions.getConstraints().get(t); 2792 // choicerow.getCells().add(gen.new Cell(null, null, e.getName().replace("[x]", Utilities.capitalize(pt.getBaseType())), definitions.getTypes().containsKey(t) ? definitions.getTypes().get(t).getDefinition() : null, null)); 2793 // choicerow.getCells().add(gen.new Cell()); 2794 // choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2795 // choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2796 // choicerow.getCells().add(gen.new Cell(null, definitions.getSrcFile(t)+".html#"+t.replace("*", "open"), t, null, null)); 2797 } else { 2798 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 2799 choicerow.getCells().add(gen.new Cell()); 2800 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2801 choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2802 choicerow.getCells().add(gen.new Cell(null, pkp.getLinkFor(corePath, t), t, null, null)); 2803 } 2804 } 2805 choicerow.getCells().add(gen.new Cell()); 2806 subRows.add(choicerow); 2807 } 2808 } 2809 2810 private boolean isReference(String t) { 2811 return t.equals("Reference") || t.equals("canonical"); 2812 } 2813 2814 2815 2816 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, FHIRException { 2817 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 2818 String s = tail(element.getPath()); 2819 List<ElementDefinition> children = getChildren(all, element); 2820 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 2821 2822 if (!onlyInformationIsMapping(all, element)) { 2823 Row row = gen.new Row(); 2824 row.setAnchor(element.getPath()); 2825 row.setColor(getRowColor(element, isConstraintMode)); 2826 if (element.hasSlicing()) 2827 row.setLineColor(1); 2828 else if (element.hasSliceName()) 2829 row.setLineColor(2); 2830 else 2831 row.setLineColor(0); 2832 boolean hasDef = element != null; 2833 String ref = defPath == null ? null : defPath + element.getId(); 2834 UnusedTracker used = new UnusedTracker(); 2835 used.used = true; 2836 Cell left = gen.new Cell(); 2837 if (element.getType().size() == 1 && element.getType().get(0).isPrimitive()) 2838 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold")); 2839 else 2840 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement()))); 2841 if (element.hasSliceName()) { 2842 left.getPieces().add(gen.new Piece("br")); 2843 String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length)); 2844 left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null)); 2845 } 2846 row.getCells().add(left); 2847 2848 ExtensionContext extDefn = null; 2849 genCardinality(gen, element, row, hasDef, used, null); 2850 if (hasDef && !"0".equals(element.getMax())) 2851 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2852 else 2853 row.getCells().add(gen.new Cell()); 2854 generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null); 2855/* if (element.hasSlicing()) { 2856 if (standardExtensionSlicing(element)) { 2857 used.used = element.hasType() && element.getType().get(0).hasProfile(); 2858 showMissing = false; 2859 } else { 2860 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 2861 row.getCells().get(2).getPieces().clear(); 2862 for (Cell cell : row.getCells()) 2863 for (Piece p : cell.getPieces()) { 2864 p.addStyle("font-style: italic"); 2865 } 2866 } 2867 }*/ 2868 rows.add(row); 2869 for (ElementDefinition child : children) 2870 if (child.getMustSupport()) 2871 genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode); 2872 } 2873 } 2874 2875 2876 private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value) { 2877 if (value.contains("#")) { 2878 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 2879 if (ext == null) 2880 return null; 2881 String tail = value.substring(value.indexOf("#")+1); 2882 ElementDefinition ed = null; 2883 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 2884 if (tail.equals(ted.getSliceName())) { 2885 ed = ted; 2886 return new ExtensionContext(ext, ed); 2887 } 2888 } 2889 return null; 2890 } else { 2891 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 2892 if (ext == null) 2893 return null; 2894 else 2895 return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0)); 2896 } 2897 } 2898 2899 2900 private boolean extensionIsComplex(String value) { 2901 if (value.contains("#")) { 2902 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 2903 if (ext == null) 2904 return false; 2905 String tail = value.substring(value.indexOf("#")+1); 2906 ElementDefinition ed = null; 2907 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 2908 if (tail.equals(ted.getSliceName())) { 2909 ed = ted; 2910 break; 2911 } 2912 } 2913 if (ed == null) 2914 return false; 2915 int i = ext.getSnapshot().getElement().indexOf(ed); 2916 int j = i+1; 2917 while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath())) 2918 j++; 2919 return j - i > 5; 2920 } else { 2921 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 2922 return ext != null && ext.getSnapshot().getElement().size() > 5; 2923 } 2924 } 2925 2926 2927 private String getRowColor(ElementDefinition element, boolean isConstraintMode) { 2928 switch (element.getUserInt(UD_ERROR_STATUS)) { 2929 case STATUS_HINT: return ROW_COLOR_HINT; 2930 case STATUS_WARNING: return ROW_COLOR_WARNING; 2931 case STATUS_ERROR: return ROW_COLOR_ERROR; 2932 case STATUS_FATAL: return ROW_COLOR_FATAL; 2933 } 2934 if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains(".")) 2935 return null; // ROW_COLOR_NOT_MUST_SUPPORT; 2936 else 2937 return null; 2938 } 2939 2940 2941 private String urltail(String path) { 2942 if (path.contains("#")) 2943 return path.substring(path.lastIndexOf('#')+1); 2944 if (path.contains("/")) 2945 return path.substring(path.lastIndexOf('/')+1); 2946 else 2947 return path; 2948 2949 } 2950 2951 private boolean standardExtensionSlicing(ElementDefinition element) { 2952 String t = tail(element.getPath()); 2953 return (t.equals("extension") || t.equals("modifierExtension")) 2954 && 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); 2955 } 2956 2957 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, boolean snapshot) throws IOException, FHIRException { 2958 return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot); 2959 } 2960 2961 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, boolean snapshot) throws IOException, FHIRException { 2962 Cell c = gen.new Cell(); 2963 row.getCells().add(c); 2964 2965 if (used) { 2966 if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) { 2967 if (root) { 2968 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 2969 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 2970 } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 2971 !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) { 2972 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 2973 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 2974 } 2975 } 2976 2977 if (definition.hasContentReference()) { 2978 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference()); 2979 if (ed == null) 2980 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null)); 2981 else 2982 c.getPieces().add(gen.new Piece("#"+ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null)); 2983 } 2984 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 2985 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 2986 } else { 2987 if (definition != null && definition.hasShort()) { 2988 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2989 c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null))); 2990 } else if (fallback != null && fallback.hasShort()) { 2991 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2992 c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null))); 2993 } 2994 if (url != null) { 2995 if (!c.getPieces().isEmpty()) 2996 c.addPiece(gen.new Piece("br")); 2997 String fullUrl = url.startsWith("#") ? baseURL+url : url; 2998 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 2999 String ref = null; 3000 String ref2 = null; 3001 String fixedUrl = null; 3002 if (ed != null) { 3003 String p = ed.getUserString("path"); 3004 if (p != null) { 3005 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 3006 } 3007 fixedUrl = getFixedUrl(ed); 3008 if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension? 3009 if (fixedUrl.equals(url)) 3010 fixedUrl = null; 3011 else { 3012 StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl); 3013 if (ed2 != null) { 3014 String p2 = ed2.getUserString("path"); 3015 if (p2 != null) { 3016 ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2); 3017 } 3018 } 3019 } 3020 } 3021 } 3022 if (fixedUrl == null) { 3023 c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold")); 3024 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3025 } else { 3026 // reference to a profile take on the extension show the base URL 3027 c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold")); 3028 c.getPieces().add(gen.new Piece(ref2, fixedUrl, null)); 3029 c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold")); 3030 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3031 3032 } 3033 } 3034 3035 if (definition.hasSlicing()) { 3036 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3037 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold")); 3038 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 3039 } 3040 if (definition != null) { 3041 ElementDefinitionBindingComponent binding = null; 3042 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 3043 binding = valueDefn.getBinding(); 3044 else if (definition.hasBinding()) 3045 binding = definition.getBinding(); 3046 if (binding!=null && !binding.isEmpty()) { 3047 if (!c.getPieces().isEmpty()) 3048 c.addPiece(gen.new Piece("br")); 3049 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 3050 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"))); 3051 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))); 3052 if (binding.hasStrength()) { 3053 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 3054 c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition()))); 3055 3056 c.getPieces().add(gen.new Piece(null, ")", null)); 3057 } 3058 if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) { 3059 br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), definition.getPath()); 3060 c.addPiece(gen.new Piece("br")); 3061 c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-maxvalueset.html", translate("sd.table", "Max Binding")+": ", "Max Value Set Extension").addStyle("font-weight:bold"))); 3062 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))); 3063 } 3064 if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) { 3065 br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), definition.getPath()); 3066 c.addPiece(gen.new Piece("br")); 3067 c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-minvalueset.html", translate("sd.table", "Min Binding")+": ", "Min Value Set Extension").addStyle("font-weight:bold"))); 3068 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))); 3069 } 3070 } 3071 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 3072 if (!inv.hasSource() || allInvariants) { 3073 if (!c.getPieces().isEmpty()) 3074 c.addPiece(gen.new Piece("br")); 3075 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 3076 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null))); 3077 } 3078 } 3079 if ((definition.hasBase() && definition.getBase().getMax().equals("*")) || (definition.hasMax() && definition.getMax().equals("*"))) { 3080 if (c.getPieces().size() > 0) 3081 c.addPiece(gen.new Piece("br")); 3082 if (definition.hasOrderMeaning()) { 3083 c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null)); 3084 } else { 3085 // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null)); 3086 } 3087 } 3088 3089 if (definition.hasFixed()) { 3090 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3091 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold"))); 3092 if (!useTableForFixedValues || definition.getFixed().isPrimitive()) { 3093 String s = buildJson(definition.getFixed()); 3094 String link = null; 3095 if (Utilities.isAbsoluteUrl(s)) 3096 link = pkp.getLinkForUrl(corePath, s); 3097 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 3098 } else { 3099 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "As shown", null).addStyle("color: darkgreen"))); 3100 genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath); 3101 } 3102 if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) { 3103 Piece p = describeCoded(gen, definition.getFixed()); 3104 if (p != null) 3105 c.getPieces().add(p); 3106 } 3107 } else if (definition.hasPattern()) { 3108 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3109 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold"))); 3110 if (!useTableForFixedValues || definition.getPattern().isPrimitive()) 3111 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 3112 else { 3113 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen"))); 3114 genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath); 3115 } 3116 } else if (definition.hasExample()) { 3117 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 3118 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3119 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold"))); 3120 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 3121 } 3122 } 3123 if (definition.hasMaxLength() && definition.getMaxLength()!=0) { 3124 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3125 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 3126 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 3127 } 3128 if (profile != null) { 3129 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 3130 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 3131 ElementDefinitionMappingComponent map = null; 3132 for (ElementDefinitionMappingComponent m : definition.getMapping()) 3133 if (m.getIdentity().equals(md.getIdentity())) 3134 map = m; 3135 if (map != null) { 3136 for (int i = 0; i<definition.getMapping().size(); i++){ 3137 c.addPiece(gen.new Piece("br")); 3138 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null)); 3139 } 3140 } 3141 } 3142 } 3143 } 3144 } 3145 } 3146 } 3147 return c; 3148 } 3149 3150 private void genFixedValue(HierarchicalTableGenerator gen, Row erow, Type value, boolean snapshot, boolean pattern, String corePath) { 3151 String ref = pkp.getLinkFor(corePath, value.fhirType()); 3152 ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#"; 3153 StructureDefinition sd = context.fetchTypeDefinition(value.fhirType()); 3154 3155 for (org.hl7.fhir.r4.model.Property t : value.children()) { 3156 if (t.getValues().size() > 0 || snapshot) { 3157 ElementDefinition ed = findElementDefinition(sd, t.getName()); 3158 if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) { 3159 Row row = gen.new Row(); 3160 erow.getSubRows().add(row); 3161 Cell c = gen.new Cell(); 3162 row.getCells().add(c); 3163 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath()), t.getName(), null)); 3164 c = gen.new Cell(); 3165 row.getCells().add(c); 3166 c.addPiece(gen.new Piece(null, null, null)); 3167 c = gen.new Cell(); 3168 row.getCells().add(c); 3169 if (!pattern) { 3170 c.addPiece(gen.new Piece(null, "0..0", null)); 3171 row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/); 3172 } else if (isPrimitive(t.getTypeCode())) { 3173 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 3174 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 3175 } else if (isReference(t.getTypeCode())) { 3176 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 3177 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 3178 } else { 3179 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 3180 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 3181 } 3182 c = gen.new Cell(); 3183 row.getCells().add(c); 3184 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null)); 3185 c = gen.new Cell(); 3186 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3187 row.getCells().add(c); 3188 } else { 3189 for (Base b : t.getValues()) { 3190 Row row = gen.new Row(); 3191 erow.getSubRows().add(row); 3192 row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/); 3193 3194 Cell c = gen.new Cell(); 3195 row.getCells().add(c); 3196 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath()), t.getName(), null)); 3197 3198 c = gen.new Cell(); 3199 row.getCells().add(c); 3200 c.addPiece(gen.new Piece(null, null, null)); 3201 3202 c = gen.new Cell(); 3203 row.getCells().add(c); 3204 if (pattern) 3205 c.addPiece(gen.new Piece(null, "1.."+(t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 3206 else 3207 c.addPiece(gen.new Piece(null, "1..1", null)); 3208 3209 c = gen.new Cell(); 3210 row.getCells().add(c); 3211 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, b.fhirType()), b.fhirType(), null)); 3212 3213 if (b.isPrimitive()) { 3214 c = gen.new Cell(); 3215 row.getCells().add(c); 3216 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3217 c.addPiece(gen.new Piece("br")); 3218 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 3219 String s = b.primitiveValue(); 3220 // ok. let's see if we can find a relevant link for this 3221 String link = null; 3222 if (Utilities.isAbsoluteUrl(s)) 3223 link = pkp.getLinkForUrl(corePath, s); 3224 c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen")); 3225 } else { 3226 c = gen.new Cell(); 3227 row.getCells().add(c); 3228 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3229 c.addPiece(gen.new Piece("br")); 3230 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 3231 c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen")); 3232 genFixedValue(gen, row, (Type) b, snapshot, pattern, corePath); 3233 } 3234 } 3235 } 3236 } 3237 } 3238 } 3239 3240 3241 private ElementDefinition findElementDefinition(StructureDefinition sd, String name) { 3242 String path = sd.getType()+"."+name; 3243 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3244 if (ed.getPath().equals(path)) 3245 return ed; 3246 } 3247 throw new FHIRException("Unable to find element "+path); 3248 } 3249 3250 3251 private String getFixedUrl(StructureDefinition sd) { 3252 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3253 if (ed.getPath().equals("Extension.url")) { 3254 if (ed.hasFixed() && ed.getFixed() instanceof UriType) 3255 return ed.getFixed().primitiveValue(); 3256 } 3257 } 3258 return null; 3259 } 3260 3261 3262 private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) { 3263 if (fixed instanceof Coding) { 3264 Coding c = (Coding) fixed; 3265 ValidationResult vr = context.validateCode(terminologyServiceOptions , c.getSystem(), c.getCode(), c.getDisplay()); 3266 if (vr.getDisplay() != null) 3267 return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); 3268 } else if (fixed instanceof CodeableConcept) { 3269 CodeableConcept cc = (CodeableConcept) fixed; 3270 for (Coding c : cc.getCoding()) { 3271 ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), c.getDisplay()); 3272 if (vr.getDisplay() != null) 3273 return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); 3274 } 3275 } 3276 return null; 3277 } 3278 3279 3280 private boolean hasDescription(Type fixed) { 3281 if (fixed instanceof Coding) { 3282 return ((Coding) fixed).hasDisplay(); 3283 } else if (fixed instanceof CodeableConcept) { 3284 CodeableConcept cc = (CodeableConcept) fixed; 3285 if (cc.hasText()) 3286 return true; 3287 for (Coding c : cc.getCoding()) 3288 if (c.hasDisplay()) 3289 return true; 3290 } // (fixed instanceof CodeType) || (fixed instanceof Quantity); 3291 return false; 3292 } 3293 3294 3295 private boolean isCoded(Type fixed) { 3296 return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity); 3297 } 3298 3299 3300 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, FHIRException { 3301 Cell c = gen.new Cell(); 3302 row.getCells().add(c); 3303 3304 if (used) { 3305 if (definition.hasContentReference()) { 3306 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference()); 3307 if (ed == null) 3308 c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null)); 3309 else 3310 c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null)); 3311 } 3312 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 3313 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 3314 } else { 3315 if (url != null) { 3316 if (!c.getPieces().isEmpty()) 3317 c.addPiece(gen.new Piece("br")); 3318 String fullUrl = url.startsWith("#") ? baseURL+url : url; 3319 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 3320 String ref = null; 3321 if (ed != null) { 3322 String p = ed.getUserString("path"); 3323 if (p != null) { 3324 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 3325 } 3326 } 3327 c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold")); 3328 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3329 } 3330 3331 if (definition.hasSlicing()) { 3332 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3333 c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); 3334 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 3335 } 3336 if (definition != null) { 3337 ElementDefinitionBindingComponent binding = null; 3338 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 3339 binding = valueDefn.getBinding(); 3340 else if (definition.hasBinding()) 3341 binding = definition.getBinding(); 3342 if (binding!=null && !binding.isEmpty()) { 3343 if (!c.getPieces().isEmpty()) 3344 c.addPiece(gen.new Piece("br")); 3345 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 3346 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold"))); 3347 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))); 3348 if (binding.hasStrength()) { 3349 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 3350 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)); 3351 } 3352 } 3353 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 3354 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3355 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 3356 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); 3357 } 3358 if (definition.hasFixed()) { 3359 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3360 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); 3361 String s = buildJson(definition.getFixed()); 3362 String link = null; 3363 if (Utilities.isAbsoluteUrl(s)) 3364 link = pkp.getLinkForUrl(corePath, s); 3365 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 3366 } else if (definition.hasPattern()) { 3367 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3368 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); 3369 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 3370 } else if (definition.hasExample()) { 3371 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 3372 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3373 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold"))); 3374 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 3375 } 3376 } 3377 if (definition.hasMaxLength() && definition.getMaxLength()!=0) { 3378 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3379 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 3380 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 3381 } 3382 if (profile != null) { 3383 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 3384 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 3385 ElementDefinitionMappingComponent map = null; 3386 for (ElementDefinitionMappingComponent m : definition.getMapping()) 3387 if (m.getIdentity().equals(md.getIdentity())) 3388 map = m; 3389 if (map != null) { 3390 for (int i = 0; i<definition.getMapping().size(); i++){ 3391 c.addPiece(gen.new Piece("br")); 3392 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null)); 3393 } 3394 } 3395 } 3396 } 3397 } 3398 if (definition.hasDefinition()) { 3399 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3400 c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold")); 3401 c.addPiece(gen.new Piece("br")); 3402 c.addMarkdown(definition.getDefinition()); 3403// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 3404 } 3405 if (definition.getComment()!=null) { 3406 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3407 c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold")); 3408 c.addPiece(gen.new Piece("br")); 3409 c.addMarkdown(definition.getComment()); 3410// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 3411 } 3412 } 3413 } 3414 } 3415 return c; 3416 } 3417 3418 3419 3420 private String buildJson(Type value) throws IOException { 3421 if (value instanceof PrimitiveType) 3422 return ((PrimitiveType) value).asStringValue(); 3423 3424 IParser json = context.newJsonParser(); 3425 return json.composeString(value, null); 3426 } 3427 3428 3429 public String describeSlice(ElementDefinitionSlicingComponent slicing) { 3430 return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator())); 3431 } 3432 3433 private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) { 3434 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 3435 for (ElementDefinitionSlicingDiscriminatorComponent id : list) 3436 c.append(id.getType().toCode()+":"+id.getPath()); 3437 return c.toString(); 3438 } 3439 3440 3441 private String describe(SlicingRules rules) { 3442 if (rules == null) 3443 return translate("sd.table", "Unspecified"); 3444 switch (rules) { 3445 case CLOSED : return translate("sd.table", "Closed"); 3446 case OPEN : return translate("sd.table", "Open"); 3447 case OPENATEND : return translate("sd.table", "Open At End"); 3448 default: 3449 return "??"; 3450 } 3451 } 3452 3453 private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) { 3454 return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) && 3455 getChildren(list, e).isEmpty(); 3456 } 3457 3458 private boolean onlyInformationIsMapping(ElementDefinition d) { 3459 return !d.hasShort() && !d.hasDefinition() && 3460 !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() && 3461 !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() && 3462 !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() && 3463 !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() && 3464 !d.hasBinding(); 3465 } 3466 3467 private boolean allAreReference(List<TypeRefComponent> types) { 3468 for (TypeRefComponent t : types) { 3469 if (!t.hasTarget()) 3470 return false; 3471 } 3472 return true; 3473 } 3474 3475 private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) { 3476 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 3477 int i = all.indexOf(element)+1; 3478 while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) { 3479 if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains(".")) 3480 result.add(all.get(i)); 3481 i++; 3482 } 3483 return result; 3484 } 3485 3486 private String tail(String path) { 3487 if (path.contains(".")) 3488 return path.substring(path.lastIndexOf('.')+1); 3489 else 3490 return path; 3491 } 3492 3493 private boolean isDataType(String value) { 3494 StructureDefinition sd = context.fetchTypeDefinition(value); 3495 if (sd == null) // might be running before all SDs are available 3496 return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", 3497 "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext"); 3498 else 3499 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 3500 } 3501 3502 private boolean isConstrainedDataType(String value) { 3503 StructureDefinition sd = context.fetchTypeDefinition(value); 3504 if (sd == null) // might be running before all SDs are available 3505 return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"); 3506 else 3507 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT; 3508 } 3509 3510 private String baseType(String value) { 3511 StructureDefinition sd = context.fetchTypeDefinition(value); 3512 if (sd != null) // might be running before all SDs are available 3513 return sd.getType(); 3514 if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity")) 3515 return "Quantity"; 3516 throw new Error("Internal error - type not known "+value); 3517 } 3518 3519 3520 public boolean isPrimitive(String value) { 3521 StructureDefinition sd = context.fetchTypeDefinition(value); 3522 if (sd == null) // might be running before all SDs are available 3523 return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid"); 3524 else 3525 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 3526 } 3527 3528// private static String listStructures(StructureDefinition p) { 3529// StringBuilder b = new StringBuilder(); 3530// boolean first = true; 3531// for (ProfileStructureComponent s : p.getStructure()) { 3532// if (first) 3533// first = false; 3534// else 3535// b.append(", "); 3536// if (pkp != null && pkp.hasLinkFor(s.getType())) 3537// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 3538// else 3539// b.append(s.getType()); 3540// } 3541// return b.toString(); 3542// } 3543 3544 3545 public StructureDefinition getProfile(StructureDefinition source, String url) { 3546 StructureDefinition profile = null; 3547 String code = null; 3548 if (url.startsWith("#")) { 3549 profile = source; 3550 code = url.substring(1); 3551 } else if (context != null) { 3552 String[] parts = url.split("\\#"); 3553 profile = context.fetchResource(StructureDefinition.class, parts[0]); 3554 code = parts.length == 1 ? null : parts[1]; 3555 } 3556 if (profile == null) 3557 return null; 3558 if (code == null) 3559 return profile; 3560 for (Resource r : profile.getContained()) { 3561 if (r instanceof StructureDefinition && r.getId().equals(code)) 3562 return (StructureDefinition) r; 3563 } 3564 return null; 3565 } 3566 3567 3568 3569 public static class ElementDefinitionHolder { 3570 private String name; 3571 private ElementDefinition self; 3572 private int baseIndex = 0; 3573 private List<ElementDefinitionHolder> children; 3574 private boolean placeHolder = false; 3575 3576 public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) { 3577 super(); 3578 this.self = self; 3579 this.name = self.getPath(); 3580 this.placeHolder = isPlaceholder; 3581 children = new ArrayList<ElementDefinitionHolder>(); 3582 } 3583 3584 public ElementDefinitionHolder(ElementDefinition self) { 3585 this(self, false); 3586 } 3587 3588 public ElementDefinition getSelf() { 3589 return self; 3590 } 3591 3592 public List<ElementDefinitionHolder> getChildren() { 3593 return children; 3594 } 3595 3596 public int getBaseIndex() { 3597 return baseIndex; 3598 } 3599 3600 public void setBaseIndex(int baseIndex) { 3601 this.baseIndex = baseIndex; 3602 } 3603 3604 public boolean isPlaceHolder() { 3605 return this.placeHolder; 3606 } 3607 3608 @Override 3609 public String toString() { 3610 if (self.hasSliceName()) 3611 return self.getPath()+"("+self.getSliceName()+")"; 3612 else 3613 return self.getPath(); 3614 } 3615 } 3616 3617 public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 3618 3619 private boolean inExtension; 3620 private List<ElementDefinition> snapshot; 3621 private int prefixLength; 3622 private String base; 3623 private String name; 3624 private Set<String> errors = new HashSet<String>(); 3625 3626 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) { 3627 this.inExtension = inExtension; 3628 this.snapshot = snapshot; 3629 this.prefixLength = prefixLength; 3630 this.base = base; 3631 this.name = name; 3632 } 3633 3634 @Override 3635 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 3636 if (o1.getBaseIndex() == 0) 3637 o1.setBaseIndex(find(o1.getSelf().getPath())); 3638 if (o2.getBaseIndex() == 0) 3639 o2.setBaseIndex(find(o2.getSelf().getPath())); 3640 return o1.getBaseIndex() - o2.getBaseIndex(); 3641 } 3642 3643 private int find(String path) { 3644 String op = path; 3645 int lc = 0; 3646 String actual = base+path.substring(prefixLength); 3647 for (int i = 0; i < snapshot.size(); i++) { 3648 String p = snapshot.get(i).getPath(); 3649 if (p.equals(actual)) { 3650 return i; 3651 } 3652 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) { 3653 return i; 3654 } 3655 if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) { 3656 String ref = snapshot.get(i).getContentReference(); 3657 if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) { 3658 actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3659 path = actual; 3660 } else { 3661 // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that 3662 actual = base+(path.substring(0, path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3663 path = actual; 3664 } 3665 3666 i = 0; 3667 lc++; 3668 if (lc > MAX_RECURSION_LIMIT) 3669 throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")"); 3670 } 3671 } 3672 if (prefixLength == 0) 3673 errors.add("Differential contains path "+path+" which is not found in the base"); 3674 else 3675 errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base"); 3676 return 0; 3677 } 3678 3679 public void checkForErrors(List<String> errorList) { 3680 if (errors.size() > 0) { 3681// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3682// for (String s : errors) 3683// b.append("StructureDefinition "+name+": "+s); 3684// throw new DefinitionException(b.toString()); 3685 for (String s : errors) 3686 if (s.startsWith("!")) 3687 errorList.add("!StructureDefinition "+name+": "+s.substring(1)); 3688 else 3689 errorList.add("StructureDefinition "+name+": "+s); 3690 } 3691 } 3692 } 3693 3694 3695 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) throws FHIRException { 3696 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 3697 int lastCount = diffList.size(); 3698 // first, we move the differential elements into a tree 3699 if (diffList.isEmpty()) 3700 return; 3701 3702 ElementDefinitionHolder edh = null; 3703 int i = 0; 3704 if (diffList.get(0).getPath().contains(".")) { 3705 String newPath = diffList.get(0).getPath().split("\\.")[0]; 3706 ElementDefinition e = new ElementDefinition(new StringType(newPath)); 3707 edh = new ElementDefinitionHolder(e, true); 3708 } else { 3709 edh = new ElementDefinitionHolder(diffList.get(0)); 3710 i = 1; 3711 } 3712 3713 boolean hasSlicing = false; 3714 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 3715 for(ElementDefinition elt : diffList) { 3716 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 3717 hasSlicing = true; 3718 break; 3719 } 3720 paths.add(elt.getPath()); 3721 } 3722 if(!hasSlicing) { 3723 // if Differential does not have slicing then safe to pre-sort the list 3724 // so elements and subcomponents are together 3725 Collections.sort(diffList, new ElementNameCompare()); 3726 } 3727 3728 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 3729 3730 // now, we sort the siblings throughout the tree 3731 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name); 3732 sortElements(edh, cmp, errors); 3733 3734 // now, we serialise them back to a list 3735 diffList.clear(); 3736 writeElements(edh, diffList); 3737 3738 if (lastCount != diffList.size()) 3739 errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal"); 3740 } 3741 3742 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 3743 String path = edh.getSelf().getPath(); 3744 final String prefix = path + "."; 3745 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 3746 if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) { 3747 String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0]; 3748 ElementDefinition e = new ElementDefinition(new StringType(newPath)); 3749 ElementDefinitionHolder child = new ElementDefinitionHolder(e, true); 3750 edh.getChildren().add(child); 3751 i = processElementsIntoTree(child, i, list); 3752 3753 } else { 3754 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 3755 edh.getChildren().add(child); 3756 i = processElementsIntoTree(child, i+1, list); 3757 } 3758 } 3759 return i; 3760 } 3761 3762 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException { 3763 if (edh.getChildren().size() == 1) 3764 // 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 3765 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath()); 3766 else 3767 Collections.sort(edh.getChildren(), cmp); 3768 cmp.checkForErrors(errors); 3769 3770 for (ElementDefinitionHolder child : edh.getChildren()) { 3771 if (child.getChildren().size() > 0) { 3772 ElementDefinitionComparer ccmp = getComparer(cmp, child); 3773 if (ccmp != null) 3774 sortElements(child, ccmp, errors); 3775 } 3776 } 3777 } 3778 3779 3780 public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error { 3781 // what we have to check for here is running off the base profile into a data type profile 3782 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 3783 ElementDefinitionComparer ccmp; 3784 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) { 3785 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name); 3786 } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) { 3787 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 3788 if (profile==null) 3789 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 3790 else 3791 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 3792 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) { 3793 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3794 if (profile==null) 3795 throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 3796 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 3797 } else if (child.getSelf().getType().size() == 1) { 3798 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode())); 3799 if (profile==null) 3800 throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 3801 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 3802 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 3803 String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3804 String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3805 String p = childLastNode.substring(edLastNode.length()-3); 3806 if (isPrimitive(Utilities.uncapitalize(p))) 3807 p = Utilities.uncapitalize(p); 3808 StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p)); 3809 if (sd == null) 3810 throw new Error("Unable to find profile '"+p+"' at "+ed.getId()); 3811 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name); 3812 } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) { 3813 for (TypeRefComponent t: child.getSelf().getType()) { 3814 if (!t.getWorkingCode().equals("Reference")) { 3815 throw new Error("Can't have children on an element with a polymorphic type - you must slice and constrain the types first (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3816 } 3817 } 3818 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3819 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 3820 } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) { 3821 for (TypeRefComponent t: ed.getType()) { 3822 if (!t.getWorkingCode().equals("Reference")) { 3823 throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3824 } 3825 } 3826 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3827 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 3828 } else { 3829 // this is allowed if we only profile the extensions 3830 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element")); 3831 if (profile==null) 3832 throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 3833 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name); 3834// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3835 } 3836 return ccmp; 3837 } 3838 3839 private static String sdNs(String type) { 3840 return sdNs(type, null); 3841 } 3842 3843 public static String sdNs(String type, String overrideVersionNs) { 3844 if (Utilities.isAbsoluteUrl(type)) 3845 return type; 3846 else if (overrideVersionNs != null) 3847 return Utilities.pathURL(overrideVersionNs, type); 3848 else 3849 return "http://hl7.org/fhir/StructureDefinition/"+type; 3850 } 3851 3852 3853 private boolean isAbstract(String code) { 3854 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource"); 3855 } 3856 3857 3858 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 3859 if (!edh.isPlaceHolder()) 3860 list.add(edh.getSelf()); 3861 for (ElementDefinitionHolder child : edh.getChildren()) { 3862 writeElements(child, list); 3863 } 3864 } 3865 3866 /** 3867 * First compare element by path then by name if same 3868 */ 3869 private static class ElementNameCompare implements Comparator<ElementDefinition> { 3870 3871 @Override 3872 public int compare(ElementDefinition o1, ElementDefinition o2) { 3873 String path1 = normalizePath(o1); 3874 String path2 = normalizePath(o2); 3875 int cmp = path1.compareTo(path2); 3876 if (cmp == 0) { 3877 String name1 = o1.hasSliceName() ? o1.getSliceName() : ""; 3878 String name2 = o2.hasSliceName() ? o2.getSliceName() : ""; 3879 cmp = name1.compareTo(name2); 3880 } 3881 return cmp; 3882 } 3883 3884 private static String normalizePath(ElementDefinition e) { 3885 if (!e.hasPath()) return ""; 3886 String path = e.getPath(); 3887 // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc. 3888 // so strip off the [x] suffix when comparing the path names. 3889 if (path.endsWith("[x]")) { 3890 path = path.substring(0, path.length()-3); 3891 } 3892 return path; 3893 } 3894 3895 } 3896 3897 3898 // generate schematrons for the rules in a structure definition 3899 public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException { 3900 if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT) 3901 throw new DefinitionException("not the right kind of structure to generate schematrons for"); 3902 if (!structure.hasSnapshot()) 3903 throw new DefinitionException("needs a snapshot"); 3904 3905 StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition()); 3906 3907 if (base != null) { 3908 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 3909 3910 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 3911 generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base); 3912 sch.dump(); 3913 } 3914 } 3915 3916 // generate a CSV representation of the structure definition 3917 public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception { 3918 if (!structure.hasSnapshot()) 3919 throw new DefinitionException("needs a snapshot"); 3920 3921 CSVWriter csv = new CSVWriter(dest, structure, asXml); 3922 3923 for (ElementDefinition child : structure.getSnapshot().getElement()) { 3924 csv.processElement(child); 3925 } 3926 csv.dump(); 3927 } 3928 3929 // generate an Excel representation of the structure definition 3930 public void generateXlsx(OutputStream dest, StructureDefinition structure, boolean asXml, boolean hideMustSupportFalse) throws IOException, DefinitionException, Exception { 3931 if (!structure.hasSnapshot()) 3932 throw new DefinitionException("needs a snapshot"); 3933 3934 XLSXWriter xlsx = new XLSXWriter(dest, structure, asXml, hideMustSupportFalse); 3935 3936 for (ElementDefinition child : structure.getSnapshot().getElement()) { 3937 xlsx.processElement(child); 3938 } 3939 xlsx.dump(); 3940 xlsx.close(); 3941 } 3942 3943 private class Slicer extends ElementDefinitionSlicingComponent { 3944 String criteria = ""; 3945 String name = ""; 3946 boolean check; 3947 public Slicer(boolean cantCheck) { 3948 super(); 3949 this.check = cantCheck; 3950 } 3951 } 3952 3953 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) { 3954 // given a child in a structure, it's sliced. figure out the slicing xpath 3955 if (child.getPath().endsWith(".extension")) { 3956 ElementDefinition ued = getUrlFor(structure, child); 3957 if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile()))) 3958 return new Slicer(false); 3959 else { 3960 Slicer s = new Slicer(true); 3961 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue(); 3962 s.name = " with URL = '"+url+"'"; 3963 s.criteria = "[@url = '"+url+"']"; 3964 return s; 3965 } 3966 } else 3967 return new Slicer(false); 3968 } 3969 3970 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException { 3971 // generateForChild(txt, structure, child); 3972 List<ElementDefinition> children = getChildList(structure, ed); 3973 String sliceName = null; 3974 ElementDefinitionSlicingComponent slicing = null; 3975 for (ElementDefinition child : children) { 3976 String name = tail(child.getPath()); 3977 if (child.hasSlicing()) { 3978 sliceName = name; 3979 slicing = child.getSlicing(); 3980 } else if (!name.equals(sliceName)) 3981 slicing = null; 3982 3983 ElementDefinition based = getByPath(base, child.getPath()); 3984 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 3985 boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax()))); 3986 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 3987 if (slicer.check) { 3988 if (doMin || doMax) { 3989 Section s = sch.section(xpath); 3990 Rule r = s.rule(xpath); 3991 if (doMin) 3992 r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin())); 3993 if (doMax) 3994 r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax()); 3995 } 3996 } 3997 } 3998 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 3999 if (inv.hasXpath()) { 4000 Section s = sch.section(ed.getPath()); 4001 Rule r = s.rule(xpath); 4002 r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 4003 } 4004 } 4005 for (ElementDefinition child : children) { 4006 String name = tail(child.getPath()); 4007 generateForChildren(sch, xpath+"/f:"+name, child, structure, base); 4008 } 4009 } 4010 4011 4012 4013 4014 private ElementDefinition getByPath(StructureDefinition base, String path) { 4015 for (ElementDefinition ed : base.getSnapshot().getElement()) { 4016 if (ed.getPath().equals(path)) 4017 return ed; 4018 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))) 4019 return ed; 4020 } 4021 return null; 4022 } 4023 4024 4025 public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException { 4026 if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { 4027 if (!sd.hasDifferential()) 4028 sd.setDifferential(new StructureDefinitionDifferentialComponent()); 4029 generateIds(sd.getDifferential().getElement(), sd.getUrl()); 4030 } 4031 if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { 4032 if (!sd.hasSnapshot()) 4033 sd.setSnapshot(new StructureDefinitionSnapshotComponent()); 4034 generateIds(sd.getSnapshot().getElement(), sd.getUrl()); 4035 } 4036 } 4037 4038 4039 private boolean hasMissingIds(List<ElementDefinition> list) { 4040 for (ElementDefinition ed : list) { 4041 if (!ed.hasId()) 4042 return true; 4043 } 4044 return false; 4045 } 4046 4047 public class SliceList { 4048 4049 private Map<String, String> slices = new HashMap<>(); 4050 4051 public void seeElement(ElementDefinition ed) { 4052 Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator(); 4053 while (iter.hasNext()) { 4054 Map.Entry<String,String> entry = iter.next(); 4055 if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath())) 4056 iter.remove(); 4057 } 4058 4059 if (ed.hasSliceName()) 4060 slices.put(ed.getPath(), ed.getSliceName()); 4061 } 4062 4063 public String[] analyse(List<String> paths) { 4064 String s = paths.get(0); 4065 String[] res = new String[paths.size()]; 4066 res[0] = null; 4067 for (int i = 1; i < paths.size(); i++) { 4068 s = s + "."+paths.get(i); 4069 if (slices.containsKey(s)) 4070 res[i] = slices.get(s); 4071 else 4072 res[i] = null; 4073 } 4074 return res; 4075 } 4076 4077 } 4078 4079 private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException { 4080 if (list.isEmpty()) 4081 return; 4082 4083 Map<String, String> idMap = new HashMap<String, String>(); 4084 Map<String, String> idList = new HashMap<String, String>(); 4085 4086 SliceList sliceInfo = new SliceList(); 4087 // first pass, update the element ids 4088 for (ElementDefinition ed : list) { 4089 List<String> paths = new ArrayList<String>(); 4090 if (!ed.hasPath()) 4091 throw new DefinitionException("No path on element Definition "+Integer.toString(list.indexOf(ed))+" in "+name); 4092 sliceInfo.seeElement(ed); 4093 String[] pl = ed.getPath().split("\\."); 4094 for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus 4095 paths.add(pl[i]); 4096 String slices[] = sliceInfo.analyse(paths); 4097 4098 StringBuilder b = new StringBuilder(); 4099 b.append(paths.get(0)); 4100 for (int i = 1; i < paths.size(); i++) { 4101 b.append("."); 4102 String s = paths.get(i); 4103 String p = slices[i]; 4104 b.append(s); 4105 if (p != null) { 4106 b.append(":"); 4107 b.append(p); 4108 } 4109 } 4110 String bs = b.toString(); 4111 idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs); 4112 ed.setId(bs); 4113 if (idList.containsKey(bs)) { 4114 if (exception || messages == null) 4115 throw new DefinitionException("Same id '"+bs+"'on multiple elements "+idList.get(bs)+"/"+ed.getPath()+" in "+name); 4116 else 4117 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR)); 4118 } 4119 idList.put(bs, ed.getPath()); 4120 if (ed.hasContentReference()) { 4121 String s = ed.getContentReference().substring(1); 4122 if (idMap.containsKey(s)) 4123 ed.setContentReference("#"+idMap.get(s)); 4124 4125 } 4126 } 4127 // second path - fix up any broken path based id references 4128 4129 } 4130 4131 4132// private String describeExtension(ElementDefinition ed) { 4133// if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) 4134// return ""; 4135// return "$"+urlTail(ed.getTypeFirstRep().getProfile()); 4136// } 4137// 4138 4139 private String urlTail(String profile) { 4140 return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile; 4141 } 4142 4143 4144 private String checkName(String name) { 4145// if (name.contains(".")) 4146//// throw new Exception("Illegal name "+name+": no '.'"); 4147// if (name.contains(" ")) 4148// throw new Exception("Illegal name "+name+": no spaces"); 4149 StringBuilder b = new StringBuilder(); 4150 for (char c : name.toCharArray()) { 4151 if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']')) 4152 b.append(c); 4153 } 4154 return b.toString().toLowerCase(); 4155 } 4156 4157 4158 private int charCount(String path, char t) { 4159 int res = 0; 4160 for (char ch : path.toCharArray()) { 4161 if (ch == t) 4162 res++; 4163 } 4164 return res; 4165 } 4166 4167// 4168//private void generateForChild(TextStreamWriter txt, 4169// StructureDefinition structure, ElementDefinition child) { 4170// // TODO Auto-generated method stub 4171// 4172//} 4173 4174 private interface ExampleValueAccessor { 4175 Type getExampleValue(ElementDefinition ed); 4176 String getId(); 4177 } 4178 4179 private class BaseExampleValueAccessor implements ExampleValueAccessor { 4180 @Override 4181 public Type getExampleValue(ElementDefinition ed) { 4182 if (ed.hasFixed()) 4183 return ed.getFixed(); 4184 if (ed.hasExample()) 4185 return ed.getExample().get(0).getValue(); 4186 else 4187 return null; 4188 } 4189 4190 @Override 4191 public String getId() { 4192 return "-genexample"; 4193 } 4194 } 4195 4196 private class ExtendedExampleValueAccessor implements ExampleValueAccessor { 4197 private String index; 4198 4199 public ExtendedExampleValueAccessor(String index) { 4200 this.index = index; 4201 } 4202 @Override 4203 public Type getExampleValue(ElementDefinition ed) { 4204 if (ed.hasFixed()) 4205 return ed.getFixed(); 4206 for (Extension ex : ed.getExtension()) { 4207 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 4208 Type value = ToolingExtensions.getExtension(ex, "exValue").getValue(); 4209 if (index.equals(ndx) && value != null) 4210 return value; 4211 } 4212 return null; 4213 } 4214 @Override 4215 public String getId() { 4216 return "-genexample-"+index; 4217 } 4218 } 4219 4220 public List<org.hl7.fhir.r4.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException { 4221 List<org.hl7.fhir.r4.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>(); 4222 if (sd.hasSnapshot()) { 4223 if (evenWhenNoExamples || hasAnyExampleValues(sd)) 4224 examples.add(generateExample(sd, new BaseExampleValueAccessor())); 4225 for (int i = 1; i <= 50; i++) { 4226 if (hasAnyExampleValues(sd, Integer.toString(i))) 4227 examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i)))); 4228 } 4229 } 4230 return examples; 4231 } 4232 4233 private org.hl7.fhir.r4.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException { 4234 ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 4235 org.hl7.fhir.r4.elementmodel.Element r = new org.hl7.fhir.r4.elementmodel.Element(ed.getPath(), new Property(context, ed, profile)); 4236 List<ElementDefinition> children = getChildMap(profile, ed); 4237 for (ElementDefinition child : children) { 4238 if (child.getPath().endsWith(".id")) { 4239 org.hl7.fhir.r4.elementmodel.Element id = new org.hl7.fhir.r4.elementmodel.Element("id", new Property(context, child, profile)); 4240 id.setValue(profile.getId()+accessor.getId()); 4241 r.getChildren().add(id); 4242 } else { 4243 org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor); 4244 if (e != null) 4245 r.getChildren().add(e); 4246 } 4247 } 4248 return r; 4249 } 4250 4251 private org.hl7.fhir.r4.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException { 4252 Type v = accessor.getExampleValue(ed); 4253 if (v != null) { 4254 return new ObjectConverter(context).convert(new Property(context, ed, profile), v); 4255 } else { 4256 org.hl7.fhir.r4.elementmodel.Element res = new org.hl7.fhir.r4.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile)); 4257 boolean hasValue = false; 4258 List<ElementDefinition> children = getChildMap(profile, ed); 4259 for (ElementDefinition child : children) { 4260 if (!child.hasContentReference()) { 4261 org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor); 4262 if (e != null) { 4263 hasValue = true; 4264 res.getChildren().add(e); 4265 } 4266 } 4267 } 4268 if (hasValue) 4269 return res; 4270 else 4271 return null; 4272 } 4273 } 4274 4275 private boolean hasAnyExampleValues(StructureDefinition sd, String index) { 4276 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4277 for (Extension ex : ed.getExtension()) { 4278 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 4279 Extension exv = ToolingExtensions.getExtension(ex, "exValue"); 4280 if (exv != null) { 4281 Type value = exv.getValue(); 4282 if (index.equals(ndx) && value != null) 4283 return true; 4284 } 4285 } 4286 return false; 4287 } 4288 4289 4290 private boolean hasAnyExampleValues(StructureDefinition sd) { 4291 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4292 if (ed.hasExample()) 4293 return true; 4294 return false; 4295 } 4296 4297 4298 public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException { 4299 sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy()); 4300 4301 if (sd.hasBaseDefinition()) { 4302 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 4303 if (base == null) 4304 throw new FHIRException("Unable to find base definition for logical model: "+sd.getBaseDefinition()+" from "+sd.getUrl()); 4305 copyElements(sd, base.getSnapshot().getElement()); 4306 } 4307 copyElements(sd, sd.getDifferential().getElement()); 4308 } 4309 4310 4311 private void copyElements(StructureDefinition sd, List<ElementDefinition> list) { 4312 for (ElementDefinition ed : list) { 4313 if (ed.getPath().contains(".")) { 4314 ElementDefinition n = ed.copy(); 4315 n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1)); 4316 sd.getSnapshot().addElement(n); 4317 } 4318 } 4319 } 4320 4321 4322 public void cleanUpDifferential(StructureDefinition sd) { 4323 if (sd.getDifferential().getElement().size() > 1) 4324 cleanUpDifferential(sd, 1); 4325 } 4326 4327 private void cleanUpDifferential(StructureDefinition sd, int start) { 4328 int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.'); 4329 int c = start; 4330 int len = sd.getDifferential().getElement().size(); 4331 HashSet<String> paths = new HashSet<String>(); 4332 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) { 4333 ElementDefinition ed = sd.getDifferential().getElement().get(c); 4334 if (!paths.contains(ed.getPath())) { 4335 paths.add(ed.getPath()); 4336 int ic = c+1; 4337 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4338 ic++; 4339 ElementDefinition slicer = null; 4340 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 4341 slices.add(ed); 4342 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) { 4343 ElementDefinition edi = sd.getDifferential().getElement().get(ic); 4344 if (ed.getPath().equals(edi.getPath())) { 4345 if (slicer == null) { 4346 slicer = new ElementDefinition(); 4347 slicer.setPath(edi.getPath()); 4348 slicer.getSlicing().setRules(SlicingRules.OPEN); 4349 sd.getDifferential().getElement().add(c, slicer); 4350 c++; 4351 ic++; 4352 } 4353 slices.add(edi); 4354 } 4355 ic++; 4356 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4357 ic++; 4358 } 4359 // now we're at the end, we're going to figure out the slicing discriminator 4360 if (slicer != null) 4361 determineSlicing(slicer, slices); 4362 } 4363 c++; 4364 if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) { 4365 cleanUpDifferential(sd, c); 4366 c++; 4367 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 4368 c++; 4369 } 4370 } 4371 } 4372 4373 4374 private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) { 4375 // first, name them 4376 int i = 0; 4377 for (ElementDefinition ed : slices) { 4378 if (ed.hasUserData("slice-name")) { 4379 ed.setSliceName(ed.getUserString("slice-name")); 4380 } else { 4381 i++; 4382 ed.setSliceName("slice-"+Integer.toString(i)); 4383 } 4384 } 4385 // now, the hard bit, how are they differentiated? 4386 // right now, we hard code this... 4387 if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension")) 4388 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 4389 else if (slicer.getPath().equals("DiagnosticReport.result")) 4390 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code"); 4391 else if (slicer.getPath().equals("Observation.related")) 4392 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code"); 4393 else if (slicer.getPath().equals("Bundle.entry")) 4394 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile"); 4395 else 4396 throw new Error("No slicing for "+slicer.getPath()); 4397 } 4398 4399 public class SpanEntry { 4400 private List<SpanEntry> children = new ArrayList<SpanEntry>(); 4401 private boolean profile; 4402 private String id; 4403 private String name; 4404 private String resType; 4405 private String cardinality; 4406 private String description; 4407 private String profileLink; 4408 private String resLink; 4409 private String type; 4410 4411 public String getName() { 4412 return name; 4413 } 4414 public void setName(String name) { 4415 this.name = name; 4416 } 4417 public String getResType() { 4418 return resType; 4419 } 4420 public void setResType(String resType) { 4421 this.resType = resType; 4422 } 4423 public String getCardinality() { 4424 return cardinality; 4425 } 4426 public void setCardinality(String cardinality) { 4427 this.cardinality = cardinality; 4428 } 4429 public String getDescription() { 4430 return description; 4431 } 4432 public void setDescription(String description) { 4433 this.description = description; 4434 } 4435 public String getProfileLink() { 4436 return profileLink; 4437 } 4438 public void setProfileLink(String profileLink) { 4439 this.profileLink = profileLink; 4440 } 4441 public String getResLink() { 4442 return resLink; 4443 } 4444 public void setResLink(String resLink) { 4445 this.resLink = resLink; 4446 } 4447 public String getId() { 4448 return id; 4449 } 4450 public void setId(String id) { 4451 this.id = id; 4452 } 4453 public boolean isProfile() { 4454 return profile; 4455 } 4456 public void setProfile(boolean profile) { 4457 this.profile = profile; 4458 } 4459 public List<SpanEntry> getChildren() { 4460 return children; 4461 } 4462 public String getType() { 4463 return type; 4464 } 4465 public void setType(String type) { 4466 this.type = type; 4467 } 4468 4469 } 4470 4471 public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException { 4472 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true); 4473 gen.setTranslator(getTranslator()); 4474 TableModel model = initSpanningTable(gen, "", false, profile.getId()); 4475 Set<String> processed = new HashSet<String>(); 4476 SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix); 4477 4478 genSpanEntry(gen, model.getRows(), span); 4479 return gen.generate(model, "", 0, outputTracker); 4480 } 4481 4482 private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException { 4483 SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile); 4484 boolean wantProcess = !processed.contains(profile.getUrl()); 4485 processed.add(profile.getUrl()); 4486 if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 4487 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 4488 if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) { 4489 String card = getCardinality(ed, profile.getSnapshot().getElement()); 4490 if (!card.endsWith(".0")) { 4491 List<String> refProfiles = listReferenceProfiles(ed); 4492 if (refProfiles.size() > 0) { 4493 String uri = refProfiles.get(0); 4494 if (uri != null) { 4495 StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri); 4496 if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) { 4497 res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix)); 4498 } 4499 } 4500 } 4501 } 4502 } 4503 } 4504 } 4505 return res; 4506 } 4507 4508 4509 private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) { 4510 int min = ed.getMin(); 4511 int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax()); 4512 while (ed != null && ed.getPath().contains(".")) { 4513 ed = findParent(ed, list); 4514 if (ed.getMax().equals("0")) 4515 max = 0; 4516 else if (!ed.getMax().equals("1") && !ed.hasSlicing()) 4517 max = Integer.MAX_VALUE; 4518 if (ed.getMin() == 0) 4519 min = 0; 4520 } 4521 return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max)); 4522 } 4523 4524 4525 private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) { 4526 int i = list.indexOf(ed)-1; 4527 while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+".")) 4528 i--; 4529 if (i == -1) 4530 return null; 4531 else 4532 return list.get(i); 4533 } 4534 4535 4536 private List<String> listReferenceProfiles(ElementDefinition ed) { 4537 List<String> res = new ArrayList<String>(); 4538 for (TypeRefComponent tr : ed.getType()) { 4539 // code is null if we're dealing with "value" and profile is null if we just have Reference() 4540 if (tr.hasTarget() && tr.hasTargetProfile()) 4541 for (UriType u : tr.getTargetProfile()) 4542 res.add(u.getValue()); 4543 } 4544 return res; 4545 } 4546 4547 4548 private String nameForElement(ElementDefinition ed) { 4549 return ed.getPath().substring(ed.getPath().indexOf(".")+1); 4550 } 4551 4552 4553 private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException { 4554 SpanEntry res = new SpanEntry(); 4555 res.setName(name); 4556 res.setCardinality(cardinality); 4557 res.setProfileLink(profile.getUserString("path")); 4558 res.setResType(profile.getType()); 4559 StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType()); 4560 if (base != null) 4561 res.setResLink(base.getUserString("path")); 4562 res.setId(profile.getId()); 4563 res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT); 4564 StringBuilder b = new StringBuilder(); 4565 b.append(res.getResType()); 4566 boolean first = true; 4567 boolean open = false; 4568 if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 4569 res.setDescription(profile.getName()); 4570 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 4571 if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) { 4572 if (first) { 4573 open = true; 4574 first = false; 4575 b.append("["); 4576 } else { 4577 b.append(", "); 4578 } 4579 b.append(tail(ed.getBase().getPath())); 4580 b.append("="); 4581 b.append(summarize(ed.getFixed())); 4582 } 4583 } 4584 if (open) 4585 b.append("]"); 4586 } else 4587 res.setDescription("Base FHIR "+profile.getName()); 4588 res.setType(b.toString()); 4589 return res ; 4590 } 4591 4592 4593 private String summarize(Type value) throws IOException { 4594 if (value instanceof Coding) 4595 return summarizeCoding((Coding) value); 4596 else if (value instanceof CodeableConcept) 4597 return summarizeCodeableConcept((CodeableConcept) value); 4598 else 4599 return buildJson(value); 4600 } 4601 4602 4603 private String summarizeCoding(Coding value) { 4604 String uri = value.getSystem(); 4605 String system = NarrativeGenerator.describeSystem(uri); 4606 if (Utilities.isURL(system)) { 4607 if (system.equals("http://cap.org/protocols")) 4608 system = "CAP Code"; 4609 } 4610 return system+" "+value.getCode(); 4611 } 4612 4613 4614 private String summarizeCodeableConcept(CodeableConcept value) { 4615 if (value.hasCoding()) 4616 return summarizeCoding(value.getCodingFirstRep()); 4617 else 4618 return value.getText(); 4619 } 4620 4621 4622 private boolean isKeyProperty(String path) { 4623 return Utilities.existsInList(path, "Observation.code"); 4624 } 4625 4626 4627 public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) { 4628 TableModel model = gen.new TableModel(id, false); 4629 4630 model.setDocoImg(prefix+"help16.png"); 4631 model.setDocoRef(prefix+"formats.html#table"); // todo: change to graph definition 4632 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0)); 4633 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)); 4634 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0)); 4635 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0)); 4636 return model; 4637 } 4638 4639 private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException { 4640 Row row = gen.new Row(); 4641 rows.add(row); 4642 row.setAnchor(span.getId()); 4643 //row.setColor(..?); 4644 if (span.isProfile()) 4645 row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE); 4646 else 4647 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 4648 4649 row.getCells().add(gen.new Cell(null, null, span.getName(), null, null)); 4650 row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null)); 4651 row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null)); 4652 row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null)); 4653 4654 for (SpanEntry child : span.getChildren()) 4655 genSpanEntry(gen, row.getSubRows(), child); 4656 } 4657 4658 4659 public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) { 4660 if (discriminator.endsWith("@pattern")) 4661 return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 4662 if (discriminator.endsWith("@profile")) 4663 return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 4664 if (discriminator.endsWith("@type")) 4665 return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6)); 4666 if (discriminator.endsWith("@exists")) 4667 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 4668 if (isExists) 4669 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 4670 return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator); 4671 } 4672 4673 4674 private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) { 4675 return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str); 4676 } 4677 4678 4679 public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException { 4680 switch (t.getType()) { 4681 case PROFILE: return t.getPath()+"/@profile"; 4682 case TYPE: return t.getPath()+"/@type"; 4683 case VALUE: return t.getPath(); 4684 case PATTERN: return t.getPath(); 4685 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 4686 default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2"); 4687 } 4688 } 4689 4690 4691 public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) { 4692 String epath = url.substring(54); 4693 if (!epath.contains(".")) 4694 return null; 4695 String type = epath.substring(0, epath.indexOf(".")); 4696 StructureDefinition sd = context.fetchTypeDefinition(type); 4697 if (sd == null) 4698 return null; 4699 ElementDefinition ed = null; 4700 for (ElementDefinition t : sd.getSnapshot().getElement()) { 4701 if (t.getPath().equals(epath)) { 4702 ed = t; 4703 break; 4704 } 4705 } 4706 if (ed == null) 4707 return null; 4708 if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) { 4709 return null; 4710 } else { 4711 StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"); 4712 StructureDefinition ext = template.copy(); 4713 ext.setUrl(url); 4714 ext.setId("extension-"+epath); 4715 ext.setName("Extension-"+epath); 4716 ext.setTitle("Extension for r4 "+epath); 4717 ext.setStatus(sd.getStatus()); 4718 ext.setDate(sd.getDate()); 4719 ext.getContact().clear(); 4720 ext.getContact().addAll(sd.getContact()); 4721 ext.setFhirVersion(sd.getFhirVersion()); 4722 ext.setDescription(ed.getDefinition()); 4723 ext.getContext().clear(); 4724 ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf("."))); 4725 ext.getDifferential().getElement().clear(); 4726 ext.getSnapshot().getElement().get(3).setFixed(new UriType(url)); 4727 ext.getSnapshot().getElement().set(4, ed.copy()); 4728 ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary())); 4729 return ext; 4730 } 4731 4732 } 4733 4734 4735 public boolean isThrowException() { 4736 return exception; 4737 } 4738 4739 4740 public void setThrowException(boolean exception) { 4741 this.exception = exception; 4742 } 4743 4744 4745 public TerminologyServiceOptions getTerminologyServiceOptions() { 4746 return terminologyServiceOptions; 4747 } 4748 4749 4750 public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) { 4751 this.terminologyServiceOptions = terminologyServiceOptions; 4752 } 4753 4754 4755 public boolean isNewSlicingProcessing() { 4756 return newSlicingProcessing; 4757 } 4758 4759 4760 public void setNewSlicingProcessing(boolean newSlicingProcessing) { 4761 this.newSlicingProcessing = newSlicingProcessing; 4762 } 4763 4764 4765 public boolean isDebug() { 4766 return debug; 4767 } 4768 4769 4770 public void setDebug(boolean debug) { 4771 this.debug = debug; 4772 } 4773 4774 4775 4776 4777}