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