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