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 // todo: what to do about conditions? 1845 // condition : id 0..* 1846 1847 if (derived.hasMustSupportElement()) { 1848 if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))) 1849 base.setMustSupportElement(derived.getMustSupportElement().copy()); 1850 else if (trimDifferential) 1851 derived.setMustSupportElement(null); 1852 else 1853 derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true); 1854 } 1855 1856 1857 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 1858 // but extensions can change isModifier 1859 if (isExtension) { 1860 if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) 1861 base.setIsModifierElement(derived.getIsModifierElement().copy()); 1862 else if (trimDifferential) 1863 derived.setIsModifierElement(null); 1864 else if (derived.hasIsModifierElement()) 1865 derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true); 1866 if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) 1867 base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy()); 1868 else if (trimDifferential) 1869 derived.setIsModifierReasonElement(null); 1870 else if (derived.hasIsModifierReasonElement()) 1871 derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true); 1872 } 1873 1874 if (derived.hasBinding()) { 1875 if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 1876 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 1877 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)); 1878// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 1879 else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) { 1880 ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet()); 1881 ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet()); 1882 if (baseVs == null) { 1883 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 1884 } else if (contextVs == null) { 1885 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING)); 1886 } else { 1887 ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false); 1888 ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false); 1889 if (expBase.getValueset() == null) 1890 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 1891 else if (expDerived.getValueset() == null) 1892 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING)); 1893 else if (!isSubset(expBase.getValueset(), expDerived.getValueset())) 1894 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)); 1895 1896 } 1897 } 1898 base.setBinding(derived.getBinding().copy()); 1899 } else if (trimDifferential) 1900 derived.setBinding(null); 1901 else 1902 derived.getBinding().setUserData(DERIVATION_EQUALS, true); 1903 } // else if (base.hasBinding() && doesn't have bindable type ) 1904 // base 1905 1906 if (derived.hasIsSummaryElement()) { 1907 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) { 1908 if (base.hasIsSummary()) 1909 throw new Error("Error in profile "+pn+" at "+derived.getPath()+": Base isSummary = "+base.getIsSummaryElement().asStringValue()+", derived isSummary = "+derived.getIsSummaryElement().asStringValue()); 1910 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 1911 } else if (trimDifferential) 1912 derived.setIsSummaryElement(null); 1913 else 1914 derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true); 1915 } 1916 1917 if (derived.hasType()) { 1918 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 1919 if (base.hasType()) { 1920 for (TypeRefComponent ts : derived.getType()) { 1921// if (!ts.hasCode()) { // ommitted in the differential; copy it over.... 1922// if (base.getType().size() > 1) 1923// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": constrained type code must be present if there are multiple types ("+base.typeSummary()+")"); 1924// if (base.getType().get(0).getCode() != null) 1925// ts.setCode(base.getType().get(0).getCode()); 1926// } 1927 boolean ok = false; 1928 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1929 String t = ts.getWorkingCode(); 1930 for (TypeRefComponent td : base.getType()) {; 1931 String tt = td.getWorkingCode(); 1932 b.append(tt); 1933 if (td.hasCode() && (tt.equals(t) || "Extension".equals(tt) || (t.equals("uri") && tt.equals("string")) || // work around for old badly generated SDs 1934 "Element".equals(tt) || "*".equals(tt) || 1935 (("Resource".equals(tt) || ("DomainResource".equals(tt)) && pkp.isResource(t))))) 1936 ok = true; 1937 } 1938 if (!ok) 1939 throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+t+" from "+b.toString()+" in "+srcSD.getUrl()); 1940 } 1941 } 1942 base.getType().clear(); 1943 for (TypeRefComponent t : derived.getType()) { 1944 TypeRefComponent tt = t.copy(); 1945// tt.setUserData(DERIVATION_EQUALS, true); 1946 base.getType().add(tt); 1947 } 1948 } 1949 else if (trimDifferential) 1950 derived.getType().clear(); 1951 else 1952 for (TypeRefComponent t : derived.getType()) 1953 t.setUserData(DERIVATION_EQUALS, true); 1954 } 1955 1956 if (derived.hasMapping()) { 1957 // todo: mappings are not cumulative - one replaces another 1958 if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) { 1959 for (ElementDefinitionMappingComponent s : derived.getMapping()) { 1960 boolean found = false; 1961 for (ElementDefinitionMappingComponent d : base.getMapping()) { 1962 found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap())); 1963 } 1964 if (!found) 1965 base.getMapping().add(s); 1966 } 1967 } 1968 else if (trimDifferential) 1969 derived.getMapping().clear(); 1970 else 1971 for (ElementDefinitionMappingComponent t : derived.getMapping()) 1972 t.setUserData(DERIVATION_EQUALS, true); 1973 } 1974 1975 // todo: constraints are cumulative. there is no replacing 1976 for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 1977 s.setUserData(IS_DERIVED, true); 1978 if (!s.hasSource()) 1979 s.setSource(base.getId()); 1980 } 1981 if (derived.hasConstraint()) { 1982 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 1983 ElementDefinitionConstraintComponent inv = s.copy(); 1984 base.getConstraint().add(inv); 1985 } 1986 } 1987 1988 // now, check that we still have a bindable type; if not, delete the binding - see task 8477 1989 if (dest.hasBinding() && !hasBindableType(dest)) 1990 dest.setBinding(null); 1991 1992 // finally, we copy any extensions from source to dest 1993 for (Extension ex : derived.getExtension()) { 1994 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ex.getUrl()); 1995 if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) 1996 ToolingExtensions.removeExtension(dest, ex.getUrl()); 1997 dest.addExtension(ex.copy()); 1998 } 1999 } 2000 } 2001 2002 private boolean hasBindableType(ElementDefinition ed) { 2003 for (TypeRefComponent tr : ed.getType()) { 2004 if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code")) 2005 return true; 2006 } 2007 return false; 2008 } 2009 2010 2011 private boolean isLargerMax(String derived, String base) { 2012 if ("*".equals(base)) 2013 return false; 2014 if ("*".equals(derived)) 2015 return true; 2016 return Integer.parseInt(derived) > Integer.parseInt(base); 2017 } 2018 2019 2020 private boolean isSubset(ValueSet expBase, ValueSet expDerived) { 2021 return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion()); 2022 } 2023 2024 2025 private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) { 2026 for (ValueSetExpansionContainsComponent cc : contains) { 2027 if (!inExpansion(cc, expansion.getContains())) 2028 return false; 2029 if (!codesInExpansion(cc.getContains(), expansion)) 2030 return false; 2031 } 2032 return true; 2033 } 2034 2035 2036 private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) { 2037 for (ValueSetExpansionContainsComponent cc1 : contains) { 2038 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) 2039 return true; 2040 if (inExpansion(cc, cc1.getContains())) 2041 return true; 2042 } 2043 return false; 2044 } 2045 2046 public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { 2047 for (ElementDefinition edb : base.getSnapshot().getElement()) { 2048 if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { 2049 ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); 2050 if (edm == null) { 2051 ElementDefinition edd = derived.getDifferential().addElement(); 2052 edd.setPath(edb.getPath()); 2053 edd.setMax("0"); 2054 } else if (edb.hasSlicing()) { 2055 closeChildren(base, edb, derived, edm); 2056 } 2057 } 2058 } 2059 sortDifferential(base, derived, derived.getName(), new ArrayList<String>()); 2060 } 2061 2062 private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) { 2063 String path = edb.getPath()+"."; 2064 int baseStart = base.getSnapshot().getElement().indexOf(edb); 2065 int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1); 2066 int diffStart = derived.getDifferential().getElement().indexOf(edm); 2067 int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1); 2068 2069 for (int cBase = baseStart; cBase < baseEnd; cBase++) { 2070 ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); 2071 if (isImmediateChild(edBase, edb)) { 2072 ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd); 2073 if (edMatch == null) { 2074 ElementDefinition edd = derived.getDifferential().addElement(); 2075 edd.setPath(edBase.getPath()); 2076 edd.setMax("0"); 2077 } else { 2078 closeChildren(base, edBase, derived, edMatch); 2079 } 2080 } 2081 } 2082 } 2083 2084 2085 2086 2087 private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) { 2088 String path = ed.getPath()+"."; 2089 while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) 2090 cursor++; 2091 return cursor; 2092 } 2093 2094 2095 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) { 2096 for (ElementDefinition t : list) 2097 if (t.getPath().equals(ed.getPath())) 2098 return t; 2099 return null; 2100 } 2101 2102 private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) { 2103 for (int i = start; i < end; i++) { 2104 ElementDefinition t = list.get(i); 2105 if (t.getPath().equals(ed.getPath())) 2106 return t; 2107 } 2108 return null; 2109 } 2110 2111 2112 private boolean isImmediateChild(ElementDefinition ed) { 2113 String p = ed.getPath(); 2114 if (!p.contains(".")) 2115 return false; 2116 p = p.substring(p.indexOf(".")+1); 2117 return !p.contains("."); 2118 } 2119 2120 private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { 2121 String p = candidate.getPath(); 2122 if (!p.contains(".")) 2123 return false; 2124 if (!p.startsWith(base.getPath()+".")) 2125 return false; 2126 p = p.substring(base.getPath().length()+1); 2127 return !p.contains("."); 2128 } 2129 2130 public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException { 2131 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 2132 gen.setTranslator(getTranslator()); 2133 TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false); 2134 2135 boolean deep = false; 2136 String m = ""; 2137 boolean vdeep = false; 2138 if (ed.getSnapshot().getElementFirstRep().getIsModifier()) 2139 m = "modifier_"; 2140 for (ElementDefinition eld : ed.getSnapshot().getElement()) { 2141 deep = deep || eld.getPath().contains("Extension.extension."); 2142 vdeep = vdeep || eld.getPath().contains("Extension.extension.extension."); 2143 } 2144 Row r = gen.new Row(); 2145 model.getRows().add(r); 2146 String en; 2147 if (!full) 2148 en = ed.getName(); 2149 else if (ed.getSnapshot().getElement().get(0).getIsModifier()) 2150 en = "modifierExtension"; 2151 else 2152 en = "extension"; 2153 2154 r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null)); 2155 r.getCells().add(gen.new Cell()); 2156 r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null)); 2157 2158 ElementDefinition ved = null; 2159 if (full || vdeep) { 2160 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 2161 2162 r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2163 List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0)); 2164 for (ElementDefinition child : children) 2165 if (!child.getPath().endsWith(".id")) 2166 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); 2167 } else if (deep) { 2168 List<ElementDefinition> children = new ArrayList<ElementDefinition>(); 2169 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 2170 if (ted.getPath().equals("Extension.extension")) 2171 children.add(ted); 2172 } 2173 2174 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 2175 r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2176 2177 for (ElementDefinition c : children) { 2178 ved = getValueFor(ed, c); 2179 ElementDefinition ued = getUrlFor(ed, c); 2180 if (ved != null && ued != null) { 2181 Row r1 = gen.new Row(); 2182 r.getSubRows().add(r1); 2183 r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null)); 2184 r1.getCells().add(gen.new Cell()); 2185 r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null)); 2186 genTypes(gen, r1, ved, defFile, ed, corePath, imagePath); 2187 Cell cell = gen.new Cell(); 2188 cell.addMarkdown(c.getDefinition()); 2189 r1.getCells().add(cell); 2190 r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2191 } 2192 } 2193 } else { 2194 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 2195 if (ted.getPath().startsWith("Extension.value")) 2196 ved = ted; 2197 } 2198 2199 genTypes(gen, r, ved, defFile, ed, corePath, imagePath); 2200 2201 r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2202 } 2203 Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null); 2204 Piece cc = gen.new Piece(null, ed.getName()+": ", null); 2205 c.addPiece(gen.new Piece("br")).addPiece(cc); 2206 c.addMarkdown(ed.getDescription()); 2207 2208 if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) { 2209 c.addPiece(gen.new Piece("br")); 2210 BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath()); 2211 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"))); 2212 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))); 2213 if (ved.getBinding().hasStrength()) { 2214 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null))); 2215 c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition()))); 2216 c.getPieces().add(gen.new Piece(null, ")", null)); 2217 } 2218 } 2219 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null)); 2220 r.getCells().add(c); 2221 2222 try { 2223 return gen.generate(model, corePath, 0, outputTracker); 2224 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2225 throw new FHIRException(e.getMessage(), e); 2226 } 2227 } 2228 2229 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 2230 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 2231 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 2232 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url")) 2233 return ed.getSnapshot().getElement().get(i); 2234 i++; 2235 } 2236 return null; 2237 } 2238 2239 private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) { 2240 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 2241 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 2242 if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value")) 2243 return ed.getSnapshot().getElement().get(i); 2244 i++; 2245 } 2246 return null; 2247 } 2248 2249 2250 private static final int AGG_NONE = 0; 2251 private static final int AGG_IND = 1; 2252 private static final int AGG_GR = 2; 2253 private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false; 2254 2255 private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath) { 2256 Cell c = gen.new Cell(); 2257 r.getCells().add(c); 2258 List<TypeRefComponent> types = e.getType(); 2259 if (!e.hasType()) { 2260 if (e.hasContentReference()) { 2261 return c; 2262 } else { 2263 ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER); 2264 if (d != null && d.hasType()) { 2265 types = new ArrayList<ElementDefinition.TypeRefComponent>(); 2266 for (TypeRefComponent tr : d.getType()) { 2267 TypeRefComponent tt = tr.copy(); 2268 tt.setUserData(DERIVATION_EQUALS, true); 2269 types.add(tt); 2270 } 2271 } else 2272 return c; 2273 } 2274 } 2275 2276 boolean first = true; 2277 2278 TypeRefComponent tl = null; 2279 for (TypeRefComponent t : types) { 2280 if (first) 2281 first = false; 2282 else 2283 c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null))); 2284 tl = t; 2285 if (t.hasTarget()) { 2286 c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null)); 2287 c.getPieces().add(gen.new Piece(null, "(", null)); 2288 boolean tfirst = true; 2289 for (UriType u : t.getTargetProfile()) { 2290 if (tfirst) 2291 tfirst = false; 2292 else 2293 c.addPiece(gen.new Piece(null, " | ", null)); 2294 genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue()); 2295 } 2296 c.getPieces().add(gen.new Piece(null, ")", null)); 2297 if (t.getAggregation().size() > 0) { 2298 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null)); 2299 boolean firstA = true; 2300 for (Enumeration<AggregationMode> a : t.getAggregation()) { 2301 if (firstA = true) 2302 firstA = false; 2303 else 2304 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null)); 2305 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue()))); 2306 } 2307 c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null)); 2308 } 2309 } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type 2310 String ref; 2311 ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue()); 2312 if (ref != null) { 2313 String[] parts = ref.split("\\|"); 2314 if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) { 2315// c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd 2316 c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode()))); 2317 } else { 2318// c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode()))); 2319 c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getWorkingCode()))); 2320 } 2321 } else 2322 c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null))); 2323 } else { 2324 String tc = t.getWorkingCode(); 2325 if (pkp != null && pkp.hasLinkFor(tc)) { 2326 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null))); 2327 } else 2328 c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null))); 2329 } 2330 } 2331 return c; 2332 } 2333 2334 2335 public void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, TypeRefComponent t, String u) { 2336 if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2337 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u); 2338 if (sd != null) { 2339 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 2340 c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null))); 2341 } else { 2342 String rn = u.substring(40); 2343 c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null))); 2344 } 2345 } else if (Utilities.isAbsoluteUrl(u)) { 2346 StructureDefinition sd = context.fetchResource(StructureDefinition.class, u); 2347 if (sd != null) { 2348 String disp = sd.hasTitle() ? sd.getTitle() : sd.getName(); 2349 String ref = pkp.getLinkForProfile(null, sd.getUrl()); 2350 if (ref.contains("|")) 2351 ref = ref.substring(0, ref.indexOf("|")); 2352 c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null))); 2353 } else 2354 c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null))); 2355 } else if (t.hasTargetProfile() && u.startsWith("#")) 2356 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.substring(1).toLowerCase()+".html", u, null))); 2357 } 2358 2359 private boolean isProfiledType(List<CanonicalType> theProfile) { 2360 for (CanonicalType next : theProfile){ 2361 if (StringUtils.defaultString(next.getValueAsString()).contains(":")) { 2362 return true; 2363 } 2364 } 2365 return false; 2366 } 2367 2368 2369 private String codeForAggregation(AggregationMode a) { 2370 switch (a) { 2371 case BUNDLED : return "b"; 2372 case CONTAINED : return "c"; 2373 case REFERENCED: return "r"; 2374 default: return "?"; 2375 } 2376 } 2377 2378 private String hintForAggregation(AggregationMode a) { 2379 if (a != null) 2380 return a.getDefinition(); 2381 else 2382 return null; 2383 } 2384 2385 2386 private String checkPrepend(String corePath, String path) { 2387 if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:"))) 2388 return corePath+path; 2389 else 2390 return path; 2391 } 2392 2393 2394 private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) { 2395 for (ElementDefinition ed : elements) 2396 if (ed.hasSliceName() && ("#"+ed.getSliceName()).equals(contentReference)) 2397 return ed; 2398 return null; 2399 } 2400 2401 private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) { 2402 for (ElementDefinition ed : elements) 2403 if (ed.hasId() && ("#"+ed.getId()).equals(contentReference)) 2404 return ed; 2405 return null; 2406 } 2407 2408 2409 public static String describeExtensionContext(StructureDefinition ext) { 2410 StringBuilder b = new StringBuilder(); 2411 b.append("Use on "); 2412 for (int i = 0; i < ext.getContext().size(); i++) { 2413 StructureDefinitionContextComponent ec = ext.getContext().get(i); 2414 if (i > 0) 2415 b.append(i < ext.getContext().size() - 1 ? ", " : " or "); 2416 b.append(ec.getType().getDisplay()); 2417 b.append(" "); 2418 b.append(ec.getExpression()); 2419 } 2420 if (ext.hasContextInvariant()) { 2421 b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = "); 2422 boolean first = true; 2423 for (StringType s : ext.getContextInvariant()) { 2424 if (first) 2425 first = false; 2426 else 2427 b.append(", "); 2428 b.append("<code>"+s.getValue()+"</code>"); 2429 } 2430 } 2431 return b.toString(); 2432 } 2433 2434 private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) { 2435 IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 2436 StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 2437 if (min.isEmpty() && fallback != null) 2438 min = fallback.getMinElement(); 2439 if (max.isEmpty() && fallback != null) 2440 max = fallback.getMaxElement(); 2441 2442 tracker.used = !max.isEmpty() && !max.getValue().equals("0"); 2443 2444 if (min.isEmpty() && max.isEmpty()) 2445 return null; 2446 else 2447 return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue()); 2448 } 2449 2450 private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) { 2451 IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 2452 StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 2453 if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 2454 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 2455 if (base.hasMinElement()) { 2456 min = base.getMinElement().copy(); 2457 min.setUserData(DERIVATION_EQUALS, true); 2458 } 2459 } 2460 if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 2461 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 2462 if (base.hasMaxElement()) { 2463 max = base.getMaxElement().copy(); 2464 max.setUserData(DERIVATION_EQUALS, true); 2465 } 2466 } 2467 if (min.isEmpty() && fallback != null) 2468 min = fallback.getMinElement(); 2469 if (max.isEmpty() && fallback != null) 2470 max = fallback.getMaxElement(); 2471 2472 if (!max.isEmpty()) 2473 tracker.used = !max.getValue().equals("0"); 2474 2475 Cell cell = gen.new Cell(null, null, null, null, null); 2476 row.getCells().add(cell); 2477 if (!min.isEmpty() || !max.isEmpty()) { 2478 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null))); 2479 cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null))); 2480 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null))); 2481 } 2482 } 2483 2484 2485 private Piece checkForNoChange(Element source, Piece piece) { 2486 if (source.hasUserData(DERIVATION_EQUALS)) { 2487 piece.addStyle("opacity: 0.4"); 2488 } 2489 return piece; 2490 } 2491 2492 private Piece checkForNoChange(Element src1, Element src2, Piece piece) { 2493 if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) { 2494 piece.addStyle("opacity: 0.5"); 2495 } 2496 return piece; 2497 } 2498 2499 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 { 2500 assert(diff != snapshot);// check it's ok to get rid of one of these 2501 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 2502 gen.setTranslator(getTranslator()); 2503 TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), false); 2504 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 2505 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2506 profiles.add(profile); 2507 if (list.isEmpty()) { 2508 ElementDefinition root = new ElementDefinition().setPath(profile.getType()); 2509 root.setId(profile.getType()); 2510 list.add(root); 2511 } 2512 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); 2513 try { 2514 return gen.generate(model, imagePath, 0, outputTracker); 2515 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2516 throw new FHIRException("Error generating table for profile " + profile.getUrl() + ": " + e.getMessage(), e); 2517 } 2518 } 2519 2520 2521 public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException { 2522 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); 2523 gen.setTranslator(getTranslator()); 2524 TableModel model = gen.initGridTable(corePath, profile.getId()); 2525 List<ElementDefinition> list = profile.getSnapshot().getElement(); 2526 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2527 profiles.add(profile); 2528 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)); 2529 try { 2530 return gen.generate(model, imagePath, 1, outputTracker); 2531 } catch (org.hl7.fhir.exceptions.FHIRException e) { 2532 throw new FHIRException(e.getMessage(), e); 2533 } 2534 } 2535 2536 2537 private boolean usesMustSupport(List<ElementDefinition> list) { 2538 for (ElementDefinition ed : list) 2539 if (ed.hasMustSupport() && ed.getMustSupport()) 2540 return true; 2541 return false; 2542 } 2543 2544 2545 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 { 2546 Row originalRow = slicingRow; 2547 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 2548 String s = tail(element.getPath()); 2549 if (element.hasSliceName()) 2550 s = s +":"+element.getSliceName(); 2551 Row typesRow = null; 2552 2553 List<ElementDefinition> children = getChildren(all, element); 2554 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 2555// if (!snapshot && isExtension && extensions != null && extensions != isExtension) 2556// return; 2557 2558 if (!onlyInformationIsMapping(all, element)) { 2559 Row row = gen.new Row(); 2560 row.setAnchor(element.getPath()); 2561 row.setColor(getRowColor(element, isConstraintMode)); 2562 if (element.hasSlicing()) 2563 row.setLineColor(1); 2564 else if (element.hasSliceName()) 2565 row.setLineColor(2); 2566 else 2567 row.setLineColor(0); 2568 boolean hasDef = element != null; 2569 boolean ext = false; 2570 if (tail(element.getPath()).equals("extension")) { 2571 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 2572 row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2573 else 2574 row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2575 ext = true; 2576 } else if (tail(element.getPath()).equals("modifierExtension")) { 2577 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 2578 row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 2579 else 2580 row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 2581 } else if (!hasDef || element.getType().size() == 0) 2582 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 2583 else if (hasDef && element.getType().size() > 1) { 2584 if (allAreReference(element.getType())) 2585 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2586 else { 2587 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 2588 typesRow = row; 2589 } 2590 } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@")) 2591 row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE); 2592 else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode())) 2593 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 2594 else if (hasDef && element.getType().get(0).hasTarget()) 2595 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2596 else if (hasDef && isDataType(element.getType().get(0).getWorkingCode())) 2597 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2598 else 2599 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 2600 String ref = defPath == null ? null : defPath + element.getId(); 2601 UnusedTracker used = new UnusedTracker(); 2602 used.used = true; 2603 if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR)) 2604 s = "@"+s; 2605 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); 2606 row.getCells().add(left); 2607 Cell gc = gen.new Cell(); 2608 row.getCells().add(gc); 2609 if (element != null && element.getIsModifier()) 2610 checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false)); 2611 if (element != null && element.getMustSupport()) 2612 checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false)); 2613 if (element != null && element.getIsSummary()) 2614 checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false)); 2615 if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty())) 2616 gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null, null, false); 2617 2618 ExtensionContext extDefn = null; 2619 if (ext) { 2620 if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) { 2621 String eurl = element.getType().get(0).getProfile().get(0).getValue(); 2622 extDefn = locateExtension(StructureDefinition.class, eurl); 2623 if (extDefn == null) { 2624 genCardinality(gen, element, row, hasDef, used, null); 2625 row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null)); 2626 generateDescription(gen, row, element, null, used.used, profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot); 2627 } else { 2628 String name = urltail(eurl); 2629 left.getPieces().get(0).setText(name); 2630 // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename")); 2631 left.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl()); 2632 genCardinality(gen, element, row, hasDef, used, extDefn.getElement()); 2633 ElementDefinition valueDefn = extDefn.getExtensionValueDefinition(); 2634 if (valueDefn != null && !"0".equals(valueDefn.getMax())) 2635 genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath); 2636 else // if it's complex, we just call it nothing 2637 // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile); 2638 row.getCells().add(gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null)); 2639 generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot); 2640 } 2641 } else { 2642 genCardinality(gen, element, row, hasDef, used, null); 2643 if ("0".equals(element.getMax())) 2644 row.getCells().add(gen.new Cell()); 2645 else 2646 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2647 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot); 2648 } 2649 } else { 2650 genCardinality(gen, element, row, hasDef, used, null); 2651 if (element.hasSlicing()) 2652 row.getCells().add(gen.new Cell(null, corePath+"profiling.html#slicing", "(Slice Definition)", null, null)); 2653 else if (hasDef && !"0".equals(element.getMax()) && typesRow == null) 2654 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2655 else 2656 row.getCells().add(gen.new Cell()); 2657 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot); 2658 } 2659 if (element.hasSlicing()) { 2660 if (standardExtensionSlicing(element)) { 2661 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(); 2662 showMissing = false; //? 2663 } else { 2664 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 2665 slicingRow = row; 2666 for (Cell cell : row.getCells()) 2667 for (Piece p : cell.getPieces()) { 2668 p.addStyle("font-style: italic"); 2669 } 2670 } 2671 } else if (element.hasSliceName()) { 2672 row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM); 2673 } 2674 if (used.used || showMissing) 2675 rows.add(row); 2676 if (!used.used && !element.hasSlicing()) { 2677 for (Cell cell : row.getCells()) 2678 for (Piece p : cell.getPieces()) { 2679 p.setStyle("text-decoration:line-through"); 2680 p.setReference(null); 2681 } 2682 } else{ 2683 if (slicingRow != originalRow && !children.isEmpty()) { 2684 // we've entered a slice; we're going to create a holder row for the slice children 2685 Row hrow = gen.new Row(); 2686 hrow.setAnchor(element.getPath()); 2687 hrow.setColor(getRowColor(element, isConstraintMode)); 2688 hrow.setLineColor(1); 2689 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 2690 hrow.getCells().add(gen.new Cell(null, null, "(All Slices)", "", null)); 2691 hrow.getCells().add(gen.new Cell()); 2692 hrow.getCells().add(gen.new Cell()); 2693 hrow.getCells().add(gen.new Cell()); 2694 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null)); 2695 row.getSubRows().add(hrow); 2696 row = hrow; 2697 } 2698 if (typesRow != null && !children.isEmpty()) { 2699 // we've entered a typing slice; we're going to create a holder row for the all types children 2700 Row hrow = gen.new Row(); 2701 hrow.setAnchor(element.getPath()); 2702 hrow.setColor(getRowColor(element, isConstraintMode)); 2703 hrow.setLineColor(1); 2704 hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 2705 hrow.getCells().add(gen.new Cell(null, null, "(All Types)", "", null)); 2706 hrow.getCells().add(gen.new Cell()); 2707 hrow.getCells().add(gen.new Cell()); 2708 hrow.getCells().add(gen.new Cell()); 2709 hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null)); 2710 row.getSubRows().add(hrow); 2711 row = hrow; 2712 } 2713 2714 Row currRow = row; 2715 for (ElementDefinition child : children) { 2716 if (!child.hasSliceName()) 2717 currRow = row; 2718 if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) 2719 currRow = genElement(defPath, gen, currRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow); 2720 } 2721// if (!snapshot && (extensions == null || !extensions)) 2722// for (ElementDefinition child : children) 2723// if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension")) 2724// genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants); 2725 } 2726 if (typesRow != null) { 2727 makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName); 2728 } 2729 } 2730 return slicingRow; 2731 } 2732 2733 private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, String corePath, String profileBaseFileName) { 2734 // create a child for each choice 2735 for (TypeRefComponent tr : element.getType()) { 2736 Row choicerow = gen.new Row(); 2737 String t = tr.getWorkingCode(); 2738 if (isReference(t)) { 2739 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null)); 2740 choicerow.getCells().add(gen.new Cell()); 2741 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2742 choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 2743 Cell c = gen.new Cell(); 2744 choicerow.getCells().add(c); 2745 if (ADD_REFERENCE_TO_TABLE) { 2746 if (tr.getWorkingCode().equals("canonical")) 2747 c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null)); 2748 else 2749 c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null)); 2750 c.getPieces().add(gen.new Piece(null, "(", null)); 2751 } 2752 boolean first = true; 2753 for (CanonicalType rt : tr.getTargetProfile()) { 2754 if (!first) 2755 c.getPieces().add(gen.new Piece(null, " | ", null)); 2756 genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue()); 2757 first = false; 2758 } 2759 if (ADD_REFERENCE_TO_TABLE) 2760 c.getPieces().add(gen.new Piece(null, ")", null)); 2761 2762 } else { 2763 StructureDefinition sd = context.fetchTypeDefinition(t); 2764 if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 2765 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 2766 choicerow.getCells().add(gen.new Cell()); 2767 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2768 choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 2769 choicerow.getCells().add(gen.new Cell(null, corePath+"datatypes.html#"+t, t, null, null)); 2770 // } else if (definitions.getConstraints().contthnsKey(t)) { 2771 // ProfiledType pt = definitions.getConstraints().get(t); 2772 // 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)); 2773 // choicerow.getCells().add(gen.new Cell()); 2774 // choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2775 // choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2776 // choicerow.getCells().add(gen.new Cell(null, definitions.getSrcFile(t)+".html#"+t.replace("*", "open"), t, null, null)); 2777 } else { 2778 choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); 2779 choicerow.getCells().add(gen.new Cell()); 2780 choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); 2781 choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 2782 choicerow.getCells().add(gen.new Cell(null, pkp.getLinkFor(corePath, t), t, null, null)); 2783 } 2784 } 2785 choicerow.getCells().add(gen.new Cell()); 2786 subRows.add(choicerow); 2787 } 2788 } 2789 2790 private boolean isReference(String t) { 2791 return t.equals("Reference") || t.equals("canonical"); 2792 } 2793 2794 2795 2796 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 { 2797 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 2798 String s = tail(element.getPath()); 2799 List<ElementDefinition> children = getChildren(all, element); 2800 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 2801 2802 if (!onlyInformationIsMapping(all, element)) { 2803 Row row = gen.new Row(); 2804 row.setAnchor(element.getPath()); 2805 row.setColor(getRowColor(element, isConstraintMode)); 2806 if (element.hasSlicing()) 2807 row.setLineColor(1); 2808 else if (element.hasSliceName()) 2809 row.setLineColor(2); 2810 else 2811 row.setLineColor(0); 2812 boolean hasDef = element != null; 2813 String ref = defPath == null ? null : defPath + element.getId(); 2814 UnusedTracker used = new UnusedTracker(); 2815 used.used = true; 2816 Cell left = gen.new Cell(); 2817 if (element.getType().size() == 1 && element.getType().get(0).isPrimitive()) 2818 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold")); 2819 else 2820 left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement()))); 2821 if (element.hasSliceName()) { 2822 left.getPieces().add(gen.new Piece("br")); 2823 String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length)); 2824 left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null)); 2825 } 2826 row.getCells().add(left); 2827 2828 ExtensionContext extDefn = null; 2829 genCardinality(gen, element, row, hasDef, used, null); 2830 if (hasDef && !"0".equals(element.getMax())) 2831 genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath); 2832 else 2833 row.getCells().add(gen.new Cell()); 2834 generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null); 2835/* if (element.hasSlicing()) { 2836 if (standardExtensionSlicing(element)) { 2837 used.used = element.hasType() && element.getType().get(0).hasProfile(); 2838 showMissing = false; 2839 } else { 2840 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 2841 row.getCells().get(2).getPieces().clear(); 2842 for (Cell cell : row.getCells()) 2843 for (Piece p : cell.getPieces()) { 2844 p.addStyle("font-style: italic"); 2845 } 2846 } 2847 }*/ 2848 rows.add(row); 2849 for (ElementDefinition child : children) 2850 if (child.getMustSupport()) 2851 genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode); 2852 } 2853 } 2854 2855 2856 private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value) { 2857 if (value.contains("#")) { 2858 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 2859 if (ext == null) 2860 return null; 2861 String tail = value.substring(value.indexOf("#")+1); 2862 ElementDefinition ed = null; 2863 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 2864 if (tail.equals(ted.getSliceName())) { 2865 ed = ted; 2866 return new ExtensionContext(ext, ed); 2867 } 2868 } 2869 return null; 2870 } else { 2871 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 2872 if (ext == null) 2873 return null; 2874 else 2875 return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0)); 2876 } 2877 } 2878 2879 2880 private boolean extensionIsComplex(String value) { 2881 if (value.contains("#")) { 2882 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 2883 if (ext == null) 2884 return false; 2885 String tail = value.substring(value.indexOf("#")+1); 2886 ElementDefinition ed = null; 2887 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 2888 if (tail.equals(ted.getSliceName())) { 2889 ed = ted; 2890 break; 2891 } 2892 } 2893 if (ed == null) 2894 return false; 2895 int i = ext.getSnapshot().getElement().indexOf(ed); 2896 int j = i+1; 2897 while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath())) 2898 j++; 2899 return j - i > 5; 2900 } else { 2901 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 2902 return ext != null && ext.getSnapshot().getElement().size() > 5; 2903 } 2904 } 2905 2906 2907 private String getRowColor(ElementDefinition element, boolean isConstraintMode) { 2908 switch (element.getUserInt(UD_ERROR_STATUS)) { 2909 case STATUS_HINT: return ROW_COLOR_HINT; 2910 case STATUS_WARNING: return ROW_COLOR_WARNING; 2911 case STATUS_ERROR: return ROW_COLOR_ERROR; 2912 case STATUS_FATAL: return ROW_COLOR_FATAL; 2913 } 2914 if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains(".")) 2915 return null; // ROW_COLOR_NOT_MUST_SUPPORT; 2916 else 2917 return null; 2918 } 2919 2920 2921 private String urltail(String path) { 2922 if (path.contains("#")) 2923 return path.substring(path.lastIndexOf('#')+1); 2924 if (path.contains("/")) 2925 return path.substring(path.lastIndexOf('/')+1); 2926 else 2927 return path; 2928 2929 } 2930 2931 private boolean standardExtensionSlicing(ElementDefinition element) { 2932 String t = tail(element.getPath()); 2933 return (t.equals("extension") || t.equals("modifierExtension")) 2934 && 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); 2935 } 2936 2937 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 { 2938 return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot); 2939 } 2940 2941 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 { 2942 Cell c = gen.new Cell(); 2943 row.getCells().add(c); 2944 2945 if (used) { 2946 if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) { 2947 if (root) { 2948 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 2949 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 2950 } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 2951 !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) { 2952 c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold")); 2953 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null)); 2954 } 2955 } 2956 2957 if (definition.hasContentReference()) { 2958 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference()); 2959 if (ed == null) 2960 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null)); 2961 else 2962 c.getPieces().add(gen.new Piece("#"+ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null)); 2963 } 2964 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 2965 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 2966 } else { 2967 if (definition != null && definition.hasShort()) { 2968 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2969 c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null))); 2970 } else if (fallback != null && fallback.hasShort()) { 2971 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 2972 c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null))); 2973 } 2974 if (url != null) { 2975 if (!c.getPieces().isEmpty()) 2976 c.addPiece(gen.new Piece("br")); 2977 String fullUrl = url.startsWith("#") ? baseURL+url : url; 2978 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 2979 String ref = null; 2980 String ref2 = null; 2981 String fixedUrl = null; 2982 if (ed != null) { 2983 String p = ed.getUserString("path"); 2984 if (p != null) { 2985 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 2986 } 2987 fixedUrl = getFixedUrl(ed); 2988 if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension? 2989 if (fixedUrl.equals(url)) 2990 fixedUrl = null; 2991 else { 2992 StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl); 2993 if (ed2 != null) { 2994 String p2 = ed2.getUserString("path"); 2995 if (p2 != null) { 2996 ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2); 2997 } 2998 } 2999 } 3000 } 3001 } 3002 if (fixedUrl == null) { 3003 c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold")); 3004 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3005 } else { 3006 // reference to a profile take on the extension show the base URL 3007 c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold")); 3008 c.getPieces().add(gen.new Piece(ref2, fixedUrl, null)); 3009 c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold")); 3010 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3011 3012 } 3013 } 3014 3015 if (definition.hasSlicing()) { 3016 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3017 c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold")); 3018 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 3019 } 3020 if (definition != null) { 3021 ElementDefinitionBindingComponent binding = null; 3022 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 3023 binding = valueDefn.getBinding(); 3024 else if (definition.hasBinding()) 3025 binding = definition.getBinding(); 3026 if (binding!=null && !binding.isEmpty()) { 3027 if (!c.getPieces().isEmpty()) 3028 c.addPiece(gen.new Piece("br")); 3029 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 3030 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"))); 3031 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))); 3032 if (binding.hasStrength()) { 3033 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 3034 c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition()))); 3035 3036 c.getPieces().add(gen.new Piece(null, ")", null)); 3037 } 3038 if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) { 3039 br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), definition.getPath()); 3040 c.addPiece(gen.new Piece("br")); 3041 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"))); 3042 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))); 3043 } 3044 if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) { 3045 br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), definition.getPath()); 3046 c.addPiece(gen.new Piece("br")); 3047 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"))); 3048 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))); 3049 } 3050 } 3051 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 3052 if (!inv.hasSource() || allInvariants) { 3053 if (!c.getPieces().isEmpty()) 3054 c.addPiece(gen.new Piece("br")); 3055 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 3056 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null))); 3057 } 3058 } 3059 if ((definition.hasBase() && definition.getBase().getMax().equals("*")) || (definition.hasMax() && definition.getMax().equals("*"))) { 3060 if (c.getPieces().size() > 0) 3061 c.addPiece(gen.new Piece("br")); 3062 if (definition.hasOrderMeaning()) { 3063 c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null)); 3064 } else { 3065 // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null)); 3066 } 3067 } 3068 3069 if (definition.hasFixed()) { 3070 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3071 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold"))); 3072 if (!useTableForFixedValues || definition.getFixed().isPrimitive()) { 3073 String s = buildJson(definition.getFixed()); 3074 String link = null; 3075 if (Utilities.isAbsoluteUrl(s)) 3076 link = pkp.getLinkForUrl(corePath, s); 3077 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 3078 } else { 3079 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "As shown", null).addStyle("color: darkgreen"))); 3080 genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath); 3081 } 3082 if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) { 3083 Piece p = describeCoded(gen, definition.getFixed()); 3084 if (p != null) 3085 c.getPieces().add(p); 3086 } 3087 } else if (definition.hasPattern()) { 3088 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3089 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold"))); 3090 if (!useTableForFixedValues || definition.getPattern().isPrimitive()) 3091 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 3092 else { 3093 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen"))); 3094 genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath); 3095 } 3096 } else if (definition.hasExample()) { 3097 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 3098 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3099 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold"))); 3100 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 3101 } 3102 } 3103 if (definition.hasMaxLength() && definition.getMaxLength()!=0) { 3104 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3105 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 3106 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 3107 } 3108 if (profile != null) { 3109 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 3110 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 3111 ElementDefinitionMappingComponent map = null; 3112 for (ElementDefinitionMappingComponent m : definition.getMapping()) 3113 if (m.getIdentity().equals(md.getIdentity())) 3114 map = m; 3115 if (map != null) { 3116 for (int i = 0; i<definition.getMapping().size(); i++){ 3117 c.addPiece(gen.new Piece("br")); 3118 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null)); 3119 } 3120 } 3121 } 3122 } 3123 } 3124 } 3125 } 3126 } 3127 return c; 3128 } 3129 3130 private void genFixedValue(HierarchicalTableGenerator gen, Row erow, Type value, boolean snapshot, boolean pattern, String corePath) { 3131 String ref = pkp.getLinkFor(corePath, value.fhirType()); 3132 ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#"; 3133 StructureDefinition sd = context.fetchTypeDefinition(value.fhirType()); 3134 3135 for (org.hl7.fhir.r4.model.Property t : value.children()) { 3136 if (t.getValues().size() > 0 || snapshot) { 3137 ElementDefinition ed = findElementDefinition(sd, t.getName()); 3138 if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) { 3139 Row row = gen.new Row(); 3140 erow.getSubRows().add(row); 3141 Cell c = gen.new Cell(); 3142 row.getCells().add(c); 3143 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath()), t.getName(), null)); 3144 c = gen.new Cell(); 3145 row.getCells().add(c); 3146 c.addPiece(gen.new Piece(null, null, null)); 3147 c = gen.new Cell(); 3148 row.getCells().add(c); 3149 if (!pattern) { 3150 c.addPiece(gen.new Piece(null, "0..0", null)); 3151 row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/); 3152 } else if (isPrimitive(t.getTypeCode())) { 3153 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 3154 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 3155 } else if (isReference(t.getTypeCode())) { 3156 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 3157 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 3158 } else { 3159 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 3160 c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null)); 3161 } 3162 c = gen.new Cell(); 3163 row.getCells().add(c); 3164 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null)); 3165 c = gen.new Cell(); 3166 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3167 row.getCells().add(c); 3168 } else { 3169 for (Base b : t.getValues()) { 3170 Row row = gen.new Row(); 3171 erow.getSubRows().add(row); 3172 row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/); 3173 3174 Cell c = gen.new Cell(); 3175 row.getCells().add(c); 3176 c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath()), t.getName(), null)); 3177 3178 c = gen.new Cell(); 3179 row.getCells().add(c); 3180 c.addPiece(gen.new Piece(null, null, null)); 3181 3182 c = gen.new Cell(); 3183 row.getCells().add(c); 3184 if (pattern) 3185 c.addPiece(gen.new Piece(null, "1.."+(t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null)); 3186 else 3187 c.addPiece(gen.new Piece(null, "1..1", null)); 3188 3189 c = gen.new Cell(); 3190 row.getCells().add(c); 3191 c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, b.fhirType()), b.fhirType(), null)); 3192 3193 if (b.isPrimitive()) { 3194 c = gen.new Cell(); 3195 row.getCells().add(c); 3196 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3197 c.addPiece(gen.new Piece("br")); 3198 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 3199 String s = b.primitiveValue(); 3200 // ok. let's see if we can find a relevant link for this 3201 String link = null; 3202 if (Utilities.isAbsoluteUrl(s)) 3203 link = pkp.getLinkForUrl(corePath, s); 3204 c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen")); 3205 } else { 3206 c = gen.new Cell(); 3207 row.getCells().add(c); 3208 c.addPiece(gen.new Piece(null, ed.getShort(), null)); 3209 c.addPiece(gen.new Piece("br")); 3210 c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold")); 3211 c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen")); 3212 genFixedValue(gen, row, (Type) b, snapshot, pattern, corePath); 3213 } 3214 } 3215 } 3216 } 3217 } 3218 } 3219 3220 3221 private ElementDefinition findElementDefinition(StructureDefinition sd, String name) { 3222 String path = sd.getType()+"."+name; 3223 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3224 if (ed.getPath().equals(path)) 3225 return ed; 3226 } 3227 throw new FHIRException("Unable to find element "+path); 3228 } 3229 3230 3231 private String getFixedUrl(StructureDefinition sd) { 3232 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3233 if (ed.getPath().equals("Extension.url")) { 3234 if (ed.hasFixed() && ed.getFixed() instanceof UriType) 3235 return ed.getFixed().primitiveValue(); 3236 } 3237 } 3238 return null; 3239 } 3240 3241 3242 private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) { 3243 if (fixed instanceof Coding) { 3244 Coding c = (Coding) fixed; 3245 ValidationResult vr = context.validateCode(terminologyServiceOptions , c.getSystem(), c.getCode(), c.getDisplay()); 3246 if (vr.getDisplay() != null) 3247 return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); 3248 } else if (fixed instanceof CodeableConcept) { 3249 CodeableConcept cc = (CodeableConcept) fixed; 3250 for (Coding c : cc.getCoding()) { 3251 ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), c.getDisplay()); 3252 if (vr.getDisplay() != null) 3253 return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen"); 3254 } 3255 } 3256 return null; 3257 } 3258 3259 3260 private boolean hasDescription(Type fixed) { 3261 if (fixed instanceof Coding) { 3262 return ((Coding) fixed).hasDisplay(); 3263 } else if (fixed instanceof CodeableConcept) { 3264 CodeableConcept cc = (CodeableConcept) fixed; 3265 if (cc.hasText()) 3266 return true; 3267 for (Coding c : cc.getCoding()) 3268 if (c.hasDisplay()) 3269 return true; 3270 } // (fixed instanceof CodeType) || (fixed instanceof Quantity); 3271 return false; 3272 } 3273 3274 3275 private boolean isCoded(Type fixed) { 3276 return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity); 3277 } 3278 3279 3280 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 { 3281 Cell c = gen.new Cell(); 3282 row.getCells().add(c); 3283 3284 if (used) { 3285 if (definition.hasContentReference()) { 3286 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference()); 3287 if (ed == null) 3288 c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null)); 3289 else 3290 c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null)); 3291 } 3292 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 3293 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 3294 } else { 3295 if (url != null) { 3296 if (!c.getPieces().isEmpty()) 3297 c.addPiece(gen.new Piece("br")); 3298 String fullUrl = url.startsWith("#") ? baseURL+url : url; 3299 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 3300 String ref = null; 3301 if (ed != null) { 3302 String p = ed.getUserString("path"); 3303 if (p != null) { 3304 ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p); 3305 } 3306 } 3307 c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold")); 3308 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 3309 } 3310 3311 if (definition.hasSlicing()) { 3312 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3313 c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); 3314 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 3315 } 3316 if (definition != null) { 3317 ElementDefinitionBindingComponent binding = null; 3318 if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty()) 3319 binding = valueDefn.getBinding(); 3320 else if (definition.hasBinding()) 3321 binding = definition.getBinding(); 3322 if (binding!=null && !binding.isEmpty()) { 3323 if (!c.getPieces().isEmpty()) 3324 c.addPiece(gen.new Piece("br")); 3325 BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath()); 3326 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold"))); 3327 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))); 3328 if (binding.hasStrength()) { 3329 c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null))); 3330 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)); 3331 } 3332 } 3333 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 3334 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3335 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 3336 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); 3337 } 3338 if (definition.hasFixed()) { 3339 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3340 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); 3341 String s = buildJson(definition.getFixed()); 3342 String link = null; 3343 if (Utilities.isAbsoluteUrl(s)) 3344 link = pkp.getLinkForUrl(corePath, s); 3345 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); 3346 } else if (definition.hasPattern()) { 3347 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3348 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); 3349 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 3350 } else if (definition.hasExample()) { 3351 for (ElementDefinitionExampleComponent ex : definition.getExample()) { 3352 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3353 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold"))); 3354 c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); 3355 } 3356 } 3357 if (definition.hasMaxLength() && definition.getMaxLength()!=0) { 3358 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3359 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); 3360 c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); 3361 } 3362 if (profile != null) { 3363 for (StructureDefinitionMappingComponent md : profile.getMapping()) { 3364 if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) { 3365 ElementDefinitionMappingComponent map = null; 3366 for (ElementDefinitionMappingComponent m : definition.getMapping()) 3367 if (m.getIdentity().equals(md.getIdentity())) 3368 map = m; 3369 if (map != null) { 3370 for (int i = 0; i<definition.getMapping().size(); i++){ 3371 c.addPiece(gen.new Piece("br")); 3372 c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null)); 3373 } 3374 } 3375 } 3376 } 3377 } 3378 if (definition.hasDefinition()) { 3379 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3380 c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold")); 3381 c.addPiece(gen.new Piece("br")); 3382 c.addMarkdown(definition.getDefinition()); 3383// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 3384 } 3385 if (definition.getComment()!=null) { 3386 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 3387 c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold")); 3388 c.addPiece(gen.new Piece("br")); 3389 c.addMarkdown(definition.getComment()); 3390// c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); 3391 } 3392 } 3393 } 3394 } 3395 return c; 3396 } 3397 3398 3399 3400 private String buildJson(Type value) throws IOException { 3401 if (value instanceof PrimitiveType) 3402 return ((PrimitiveType) value).asStringValue(); 3403 3404 IParser json = context.newJsonParser(); 3405 return json.composeString(value, null); 3406 } 3407 3408 3409 public String describeSlice(ElementDefinitionSlicingComponent slicing) { 3410 return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator())); 3411 } 3412 3413 private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) { 3414 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 3415 for (ElementDefinitionSlicingDiscriminatorComponent id : list) 3416 c.append(id.getType().toCode()+":"+id.getPath()); 3417 return c.toString(); 3418 } 3419 3420 3421 private String describe(SlicingRules rules) { 3422 if (rules == null) 3423 return translate("sd.table", "Unspecified"); 3424 switch (rules) { 3425 case CLOSED : return translate("sd.table", "Closed"); 3426 case OPEN : return translate("sd.table", "Open"); 3427 case OPENATEND : return translate("sd.table", "Open At End"); 3428 default: 3429 return "??"; 3430 } 3431 } 3432 3433 private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) { 3434 return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) && 3435 getChildren(list, e).isEmpty(); 3436 } 3437 3438 private boolean onlyInformationIsMapping(ElementDefinition d) { 3439 return !d.hasShort() && !d.hasDefinition() && 3440 !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() && 3441 !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() && 3442 !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() && 3443 !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() && 3444 !d.hasBinding(); 3445 } 3446 3447 private boolean allAreReference(List<TypeRefComponent> types) { 3448 for (TypeRefComponent t : types) { 3449 if (!t.hasTarget()) 3450 return false; 3451 } 3452 return true; 3453 } 3454 3455 private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) { 3456 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 3457 int i = all.indexOf(element)+1; 3458 while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) { 3459 if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains(".")) 3460 result.add(all.get(i)); 3461 i++; 3462 } 3463 return result; 3464 } 3465 3466 private String tail(String path) { 3467 if (path.contains(".")) 3468 return path.substring(path.lastIndexOf('.')+1); 3469 else 3470 return path; 3471 } 3472 3473 private boolean isDataType(String value) { 3474 StructureDefinition sd = context.fetchTypeDefinition(value); 3475 if (sd == null) // might be running before all SDs are available 3476 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", 3477 "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext"); 3478 else 3479 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 3480 } 3481 3482 private boolean isConstrainedDataType(String value) { 3483 StructureDefinition sd = context.fetchTypeDefinition(value); 3484 if (sd == null) // might be running before all SDs are available 3485 return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"); 3486 else 3487 return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT; 3488 } 3489 3490 private String baseType(String value) { 3491 StructureDefinition sd = context.fetchTypeDefinition(value); 3492 if (sd != null) // might be running before all SDs are available 3493 return sd.getType(); 3494 if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity")) 3495 return "Quantity"; 3496 throw new Error("Internal error - type not known "+value); 3497 } 3498 3499 3500 public boolean isPrimitive(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, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid"); 3504 else 3505 return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 3506 } 3507 3508// private static String listStructures(StructureDefinition p) { 3509// StringBuilder b = new StringBuilder(); 3510// boolean first = true; 3511// for (ProfileStructureComponent s : p.getStructure()) { 3512// if (first) 3513// first = false; 3514// else 3515// b.append(", "); 3516// if (pkp != null && pkp.hasLinkFor(s.getType())) 3517// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 3518// else 3519// b.append(s.getType()); 3520// } 3521// return b.toString(); 3522// } 3523 3524 3525 public StructureDefinition getProfile(StructureDefinition source, String url) { 3526 StructureDefinition profile = null; 3527 String code = null; 3528 if (url.startsWith("#")) { 3529 profile = source; 3530 code = url.substring(1); 3531 } else if (context != null) { 3532 String[] parts = url.split("\\#"); 3533 profile = context.fetchResource(StructureDefinition.class, parts[0]); 3534 code = parts.length == 1 ? null : parts[1]; 3535 } 3536 if (profile == null) 3537 return null; 3538 if (code == null) 3539 return profile; 3540 for (Resource r : profile.getContained()) { 3541 if (r instanceof StructureDefinition && r.getId().equals(code)) 3542 return (StructureDefinition) r; 3543 } 3544 return null; 3545 } 3546 3547 3548 3549 public static class ElementDefinitionHolder { 3550 private String name; 3551 private ElementDefinition self; 3552 private int baseIndex = 0; 3553 private List<ElementDefinitionHolder> children; 3554 private boolean placeHolder = false; 3555 3556 public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) { 3557 super(); 3558 this.self = self; 3559 this.name = self.getPath(); 3560 this.placeHolder = isPlaceholder; 3561 children = new ArrayList<ElementDefinitionHolder>(); 3562 } 3563 3564 public ElementDefinitionHolder(ElementDefinition self) { 3565 this(self, false); 3566 } 3567 3568 public ElementDefinition getSelf() { 3569 return self; 3570 } 3571 3572 public List<ElementDefinitionHolder> getChildren() { 3573 return children; 3574 } 3575 3576 public int getBaseIndex() { 3577 return baseIndex; 3578 } 3579 3580 public void setBaseIndex(int baseIndex) { 3581 this.baseIndex = baseIndex; 3582 } 3583 3584 public boolean isPlaceHolder() { 3585 return this.placeHolder; 3586 } 3587 3588 @Override 3589 public String toString() { 3590 if (self.hasSliceName()) 3591 return self.getPath()+"("+self.getSliceName()+")"; 3592 else 3593 return self.getPath(); 3594 } 3595 } 3596 3597 public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 3598 3599 private boolean inExtension; 3600 private List<ElementDefinition> snapshot; 3601 private int prefixLength; 3602 private String base; 3603 private String name; 3604 private Set<String> errors = new HashSet<String>(); 3605 3606 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) { 3607 this.inExtension = inExtension; 3608 this.snapshot = snapshot; 3609 this.prefixLength = prefixLength; 3610 this.base = base; 3611 this.name = name; 3612 } 3613 3614 @Override 3615 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 3616 if (o1.getBaseIndex() == 0) 3617 o1.setBaseIndex(find(o1.getSelf().getPath())); 3618 if (o2.getBaseIndex() == 0) 3619 o2.setBaseIndex(find(o2.getSelf().getPath())); 3620 return o1.getBaseIndex() - o2.getBaseIndex(); 3621 } 3622 3623 private int find(String path) { 3624 String op = path; 3625 int lc = 0; 3626 String actual = base+path.substring(prefixLength); 3627 for (int i = 0; i < snapshot.size(); i++) { 3628 String p = snapshot.get(i).getPath(); 3629 if (p.equals(actual)) { 3630 return i; 3631 } 3632 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) { 3633 return i; 3634 } 3635 if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) { 3636 String ref = snapshot.get(i).getContentReference(); 3637 if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) { 3638 actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3639 path = actual; 3640 } else { 3641 // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that 3642 actual = base+(path.substring(0, path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength); 3643 path = actual; 3644 } 3645 3646 i = 0; 3647 lc++; 3648 if (lc > MAX_RECURSION_LIMIT) 3649 throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")"); 3650 } 3651 } 3652 if (prefixLength == 0) 3653 errors.add("Differential contains path "+path+" which is not found in the base"); 3654 else 3655 errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base"); 3656 return 0; 3657 } 3658 3659 public void checkForErrors(List<String> errorList) { 3660 if (errors.size() > 0) { 3661// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 3662// for (String s : errors) 3663// b.append("StructureDefinition "+name+": "+s); 3664// throw new DefinitionException(b.toString()); 3665 for (String s : errors) 3666 if (s.startsWith("!")) 3667 errorList.add("!StructureDefinition "+name+": "+s.substring(1)); 3668 else 3669 errorList.add("StructureDefinition "+name+": "+s); 3670 } 3671 } 3672 } 3673 3674 3675 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) throws FHIRException { 3676 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 3677 int lastCount = diffList.size(); 3678 // first, we move the differential elements into a tree 3679 if (diffList.isEmpty()) 3680 return; 3681 3682 ElementDefinitionHolder edh = null; 3683 int i = 0; 3684 if (diffList.get(0).getPath().contains(".")) { 3685 String newPath = diffList.get(0).getPath().split("\\.")[0]; 3686 ElementDefinition e = new ElementDefinition(new StringType(newPath)); 3687 edh = new ElementDefinitionHolder(e, true); 3688 } else { 3689 edh = new ElementDefinitionHolder(diffList.get(0)); 3690 i = 1; 3691 } 3692 3693 boolean hasSlicing = false; 3694 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 3695 for(ElementDefinition elt : diffList) { 3696 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 3697 hasSlicing = true; 3698 break; 3699 } 3700 paths.add(elt.getPath()); 3701 } 3702 if(!hasSlicing) { 3703 // if Differential does not have slicing then safe to pre-sort the list 3704 // so elements and subcomponents are together 3705 Collections.sort(diffList, new ElementNameCompare()); 3706 } 3707 3708 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 3709 3710 // now, we sort the siblings throughout the tree 3711 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name); 3712 sortElements(edh, cmp, errors); 3713 3714 // now, we serialise them back to a list 3715 diffList.clear(); 3716 writeElements(edh, diffList); 3717 3718 if (lastCount != diffList.size()) 3719 errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal"); 3720 } 3721 3722 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 3723 String path = edh.getSelf().getPath(); 3724 final String prefix = path + "."; 3725 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 3726 if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) { 3727 String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0]; 3728 ElementDefinition e = new ElementDefinition(new StringType(newPath)); 3729 ElementDefinitionHolder child = new ElementDefinitionHolder(e, true); 3730 edh.getChildren().add(child); 3731 i = processElementsIntoTree(child, i, list); 3732 3733 } else { 3734 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 3735 edh.getChildren().add(child); 3736 i = processElementsIntoTree(child, i+1, list); 3737 } 3738 } 3739 return i; 3740 } 3741 3742 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException { 3743 if (edh.getChildren().size() == 1) 3744 // 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 3745 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath()); 3746 else 3747 Collections.sort(edh.getChildren(), cmp); 3748 cmp.checkForErrors(errors); 3749 3750 for (ElementDefinitionHolder child : edh.getChildren()) { 3751 if (child.getChildren().size() > 0) { 3752 ElementDefinitionComparer ccmp = getComparer(cmp, child); 3753 if (ccmp != null) 3754 sortElements(child, ccmp, errors); 3755 } 3756 } 3757 } 3758 3759 3760 public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error { 3761 // what we have to check for here is running off the base profile into a data type profile 3762 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 3763 ElementDefinitionComparer ccmp; 3764 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) { 3765 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name); 3766 } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) { 3767 StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); 3768 if (profile==null) 3769 ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case 3770 else 3771 ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 3772 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) { 3773 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3774 if (profile==null) 3775 throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 3776 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 3777 } else if (child.getSelf().getType().size() == 1) { 3778 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode())); 3779 if (profile==null) 3780 throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 3781 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 3782 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 3783 String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3784 String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2"); 3785 String p = childLastNode.substring(edLastNode.length()-3); 3786 if (isPrimitive(Utilities.uncapitalize(p))) 3787 p = Utilities.uncapitalize(p); 3788 StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p)); 3789 if (sd == null) 3790 throw new Error("Unable to find profile '"+p+"' at "+ed.getId()); 3791 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name); 3792 } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) { 3793 for (TypeRefComponent t: child.getSelf().getType()) { 3794 if (!t.getWorkingCode().equals("Reference")) { 3795 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())+")"); 3796 } 3797 } 3798 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3799 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 3800 } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) { 3801 for (TypeRefComponent t: ed.getType()) { 3802 if (!t.getWorkingCode().equals("Reference")) { 3803 throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3804 } 3805 } 3806 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode())); 3807 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name); 3808 } else { 3809 // this is allowed if we only profile the extensions 3810 StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element")); 3811 if (profile==null) 3812 throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath()); 3813 ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name); 3814// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 3815 } 3816 return ccmp; 3817 } 3818 3819 private static String sdNs(String type) { 3820 return sdNs(type, null); 3821 } 3822 3823 public static String sdNs(String type, String overrideVersionNs) { 3824 if (Utilities.isAbsoluteUrl(type)) 3825 return type; 3826 else if (overrideVersionNs != null) 3827 return Utilities.pathURL(overrideVersionNs, type); 3828 else 3829 return "http://hl7.org/fhir/StructureDefinition/"+type; 3830 } 3831 3832 3833 private boolean isAbstract(String code) { 3834 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource"); 3835 } 3836 3837 3838 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 3839 if (!edh.isPlaceHolder()) 3840 list.add(edh.getSelf()); 3841 for (ElementDefinitionHolder child : edh.getChildren()) { 3842 writeElements(child, list); 3843 } 3844 } 3845 3846 /** 3847 * First compare element by path then by name if same 3848 */ 3849 private static class ElementNameCompare implements Comparator<ElementDefinition> { 3850 3851 @Override 3852 public int compare(ElementDefinition o1, ElementDefinition o2) { 3853 String path1 = normalizePath(o1); 3854 String path2 = normalizePath(o2); 3855 int cmp = path1.compareTo(path2); 3856 if (cmp == 0) { 3857 String name1 = o1.hasSliceName() ? o1.getSliceName() : ""; 3858 String name2 = o2.hasSliceName() ? o2.getSliceName() : ""; 3859 cmp = name1.compareTo(name2); 3860 } 3861 return cmp; 3862 } 3863 3864 private static String normalizePath(ElementDefinition e) { 3865 if (!e.hasPath()) return ""; 3866 String path = e.getPath(); 3867 // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc. 3868 // so strip off the [x] suffix when comparing the path names. 3869 if (path.endsWith("[x]")) { 3870 path = path.substring(0, path.length()-3); 3871 } 3872 return path; 3873 } 3874 3875 } 3876 3877 3878 // generate schematrons for the rules in a structure definition 3879 public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException { 3880 if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT) 3881 throw new DefinitionException("not the right kind of structure to generate schematrons for"); 3882 if (!structure.hasSnapshot()) 3883 throw new DefinitionException("needs a snapshot"); 3884 3885 StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition()); 3886 3887 if (base != null) { 3888 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 3889 3890 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 3891 generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base); 3892 sch.dump(); 3893 } 3894 } 3895 3896 // generate a CSV representation of the structure definition 3897 public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception { 3898 if (!structure.hasSnapshot()) 3899 throw new DefinitionException("needs a snapshot"); 3900 3901 CSVWriter csv = new CSVWriter(dest, structure, asXml); 3902 3903 for (ElementDefinition child : structure.getSnapshot().getElement()) { 3904 csv.processElement(child); 3905 } 3906 csv.dump(); 3907 } 3908 3909 // generate an Excel representation of the structure definition 3910 public void generateXlsx(OutputStream dest, StructureDefinition structure, boolean asXml, boolean hideMustSupportFalse) throws IOException, DefinitionException, Exception { 3911 if (!structure.hasSnapshot()) 3912 throw new DefinitionException("needs a snapshot"); 3913 3914 XLSXWriter xlsx = new XLSXWriter(dest, structure, asXml, hideMustSupportFalse); 3915 3916 for (ElementDefinition child : structure.getSnapshot().getElement()) { 3917 xlsx.processElement(child); 3918 } 3919 xlsx.dump(); 3920 xlsx.close(); 3921 } 3922 3923 private class Slicer extends ElementDefinitionSlicingComponent { 3924 String criteria = ""; 3925 String name = ""; 3926 boolean check; 3927 public Slicer(boolean cantCheck) { 3928 super(); 3929 this.check = cantCheck; 3930 } 3931 } 3932 3933 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) { 3934 // given a child in a structure, it's sliced. figure out the slicing xpath 3935 if (child.getPath().endsWith(".extension")) { 3936 ElementDefinition ued = getUrlFor(structure, child); 3937 if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile()))) 3938 return new Slicer(false); 3939 else { 3940 Slicer s = new Slicer(true); 3941 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue(); 3942 s.name = " with URL = '"+url+"'"; 3943 s.criteria = "[@url = '"+url+"']"; 3944 return s; 3945 } 3946 } else 3947 return new Slicer(false); 3948 } 3949 3950 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException { 3951 // generateForChild(txt, structure, child); 3952 List<ElementDefinition> children = getChildList(structure, ed); 3953 String sliceName = null; 3954 ElementDefinitionSlicingComponent slicing = null; 3955 for (ElementDefinition child : children) { 3956 String name = tail(child.getPath()); 3957 if (child.hasSlicing()) { 3958 sliceName = name; 3959 slicing = child.getSlicing(); 3960 } else if (!name.equals(sliceName)) 3961 slicing = null; 3962 3963 ElementDefinition based = getByPath(base, child.getPath()); 3964 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 3965 boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax()))); 3966 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 3967 if (slicer.check) { 3968 if (doMin || doMax) { 3969 Section s = sch.section(xpath); 3970 Rule r = s.rule(xpath); 3971 if (doMin) 3972 r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin())); 3973 if (doMax) 3974 r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax()); 3975 } 3976 } 3977 } 3978 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 3979 if (inv.hasXpath()) { 3980 Section s = sch.section(ed.getPath()); 3981 Rule r = s.rule(xpath); 3982 r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 3983 } 3984 } 3985 for (ElementDefinition child : children) { 3986 String name = tail(child.getPath()); 3987 generateForChildren(sch, xpath+"/f:"+name, child, structure, base); 3988 } 3989 } 3990 3991 3992 3993 3994 private ElementDefinition getByPath(StructureDefinition base, String path) { 3995 for (ElementDefinition ed : base.getSnapshot().getElement()) { 3996 if (ed.getPath().equals(path)) 3997 return ed; 3998 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))) 3999 return ed; 4000 } 4001 return null; 4002 } 4003 4004 4005 public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException { 4006 if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { 4007 if (!sd.hasDifferential()) 4008 sd.setDifferential(new StructureDefinitionDifferentialComponent()); 4009 generateIds(sd.getDifferential().getElement(), sd.getUrl()); 4010 } 4011 if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { 4012 if (!sd.hasSnapshot()) 4013 sd.setSnapshot(new StructureDefinitionSnapshotComponent()); 4014 generateIds(sd.getSnapshot().getElement(), sd.getUrl()); 4015 } 4016 } 4017 4018 4019 private boolean hasMissingIds(List<ElementDefinition> list) { 4020 for (ElementDefinition ed : list) { 4021 if (!ed.hasId()) 4022 return true; 4023 } 4024 return false; 4025 } 4026 4027 public class SliceList { 4028 4029 private Map<String, String> slices = new HashMap<>(); 4030 4031 public void seeElement(ElementDefinition ed) { 4032 Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator(); 4033 while (iter.hasNext()) { 4034 Map.Entry<String,String> entry = iter.next(); 4035 if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath())) 4036 iter.remove(); 4037 } 4038 4039 if (ed.hasSliceName()) 4040 slices.put(ed.getPath(), ed.getSliceName()); 4041 } 4042 4043 public String[] analyse(List<String> paths) { 4044 String s = paths.get(0); 4045 String[] res = new String[paths.size()]; 4046 res[0] = null; 4047 for (int i = 1; i < paths.size(); i++) { 4048 s = s + "."+paths.get(i); 4049 if (slices.containsKey(s)) 4050 res[i] = slices.get(s); 4051 else 4052 res[i] = null; 4053 } 4054 return res; 4055 } 4056 4057 } 4058 4059 private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException { 4060 if (list.isEmpty()) 4061 return; 4062 4063 Map<String, String> idMap = new HashMap<String, String>(); 4064 Map<String, String> idList = new HashMap<String, String>(); 4065 4066 SliceList sliceInfo = new SliceList(); 4067 // first pass, update the element ids 4068 for (ElementDefinition ed : list) { 4069 List<String> paths = new ArrayList<String>(); 4070 if (!ed.hasPath()) 4071 throw new DefinitionException("No path on element Definition "+Integer.toString(list.indexOf(ed))+" in "+name); 4072 sliceInfo.seeElement(ed); 4073 String[] pl = ed.getPath().split("\\."); 4074 for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus 4075 paths.add(pl[i]); 4076 String slices[] = sliceInfo.analyse(paths); 4077 4078 StringBuilder b = new StringBuilder(); 4079 b.append(paths.get(0)); 4080 for (int i = 1; i < paths.size(); i++) { 4081 b.append("."); 4082 String s = paths.get(i); 4083 String p = slices[i]; 4084 b.append(s); 4085 if (p != null) { 4086 b.append(":"); 4087 b.append(p); 4088 } 4089 } 4090 String bs = b.toString(); 4091 idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs); 4092 ed.setId(bs); 4093 if (idList.containsKey(bs)) { 4094 if (exception || messages == null) 4095 throw new DefinitionException("Same id '"+bs+"'on multiple elements "+idList.get(bs)+"/"+ed.getPath()+" in "+name); 4096 else 4097 messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR)); 4098 } 4099 idList.put(bs, ed.getPath()); 4100 if (ed.hasContentReference()) { 4101 String s = ed.getContentReference().substring(1); 4102 if (idMap.containsKey(s)) 4103 ed.setContentReference("#"+idMap.get(s)); 4104 4105 } 4106 } 4107 // second path - fix up any broken path based id references 4108 4109 } 4110 4111 4112// private String describeExtension(ElementDefinition ed) { 4113// if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) 4114// return ""; 4115// return "$"+urlTail(ed.getTypeFirstRep().getProfile()); 4116// } 4117// 4118 4119 private String urlTail(String profile) { 4120 return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile; 4121 } 4122 4123 4124 private String checkName(String name) { 4125// if (name.contains(".")) 4126//// throw new Exception("Illegal name "+name+": no '.'"); 4127// if (name.contains(" ")) 4128// throw new Exception("Illegal name "+name+": no spaces"); 4129 StringBuilder b = new StringBuilder(); 4130 for (char c : name.toCharArray()) { 4131 if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']')) 4132 b.append(c); 4133 } 4134 return b.toString().toLowerCase(); 4135 } 4136 4137 4138 private int charCount(String path, char t) { 4139 int res = 0; 4140 for (char ch : path.toCharArray()) { 4141 if (ch == t) 4142 res++; 4143 } 4144 return res; 4145 } 4146 4147// 4148//private void generateForChild(TextStreamWriter txt, 4149// StructureDefinition structure, ElementDefinition child) { 4150// // TODO Auto-generated method stub 4151// 4152//} 4153 4154 private interface ExampleValueAccessor { 4155 Type getExampleValue(ElementDefinition ed); 4156 String getId(); 4157 } 4158 4159 private class BaseExampleValueAccessor implements ExampleValueAccessor { 4160 @Override 4161 public Type getExampleValue(ElementDefinition ed) { 4162 if (ed.hasFixed()) 4163 return ed.getFixed(); 4164 if (ed.hasExample()) 4165 return ed.getExample().get(0).getValue(); 4166 else 4167 return null; 4168 } 4169 4170 @Override 4171 public String getId() { 4172 return "-genexample"; 4173 } 4174 } 4175 4176 private class ExtendedExampleValueAccessor implements ExampleValueAccessor { 4177 private String index; 4178 4179 public ExtendedExampleValueAccessor(String index) { 4180 this.index = index; 4181 } 4182 @Override 4183 public Type getExampleValue(ElementDefinition ed) { 4184 if (ed.hasFixed()) 4185 return ed.getFixed(); 4186 for (Extension ex : ed.getExtension()) { 4187 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 4188 Type value = ToolingExtensions.getExtension(ex, "exValue").getValue(); 4189 if (index.equals(ndx) && value != null) 4190 return value; 4191 } 4192 return null; 4193 } 4194 @Override 4195 public String getId() { 4196 return "-genexample-"+index; 4197 } 4198 } 4199 4200 public List<org.hl7.fhir.r4.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException { 4201 List<org.hl7.fhir.r4.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>(); 4202 if (sd.hasSnapshot()) { 4203 if (evenWhenNoExamples || hasAnyExampleValues(sd)) 4204 examples.add(generateExample(sd, new BaseExampleValueAccessor())); 4205 for (int i = 1; i <= 50; i++) { 4206 if (hasAnyExampleValues(sd, Integer.toString(i))) 4207 examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i)))); 4208 } 4209 } 4210 return examples; 4211 } 4212 4213 private org.hl7.fhir.r4.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException { 4214 ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 4215 org.hl7.fhir.r4.elementmodel.Element r = new org.hl7.fhir.r4.elementmodel.Element(ed.getPath(), new Property(context, ed, profile)); 4216 List<ElementDefinition> children = getChildMap(profile, ed); 4217 for (ElementDefinition child : children) { 4218 if (child.getPath().endsWith(".id")) { 4219 org.hl7.fhir.r4.elementmodel.Element id = new org.hl7.fhir.r4.elementmodel.Element("id", new Property(context, child, profile)); 4220 id.setValue(profile.getId()+accessor.getId()); 4221 r.getChildren().add(id); 4222 } else { 4223 org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor); 4224 if (e != null) 4225 r.getChildren().add(e); 4226 } 4227 } 4228 return r; 4229 } 4230 4231 private org.hl7.fhir.r4.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException { 4232 Type v = accessor.getExampleValue(ed); 4233 if (v != null) { 4234 return new ObjectConverter(context).convert(new Property(context, ed, profile), v); 4235 } else { 4236 org.hl7.fhir.r4.elementmodel.Element res = new org.hl7.fhir.r4.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile)); 4237 boolean hasValue = false; 4238 List<ElementDefinition> children = getChildMap(profile, ed); 4239 for (ElementDefinition child : children) { 4240 if (!child.hasContentReference()) { 4241 org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor); 4242 if (e != null) { 4243 hasValue = true; 4244 res.getChildren().add(e); 4245 } 4246 } 4247 } 4248 if (hasValue) 4249 return res; 4250 else 4251 return null; 4252 } 4253 } 4254 4255 private boolean hasAnyExampleValues(StructureDefinition sd, String index) { 4256 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4257 for (Extension ex : ed.getExtension()) { 4258 String ndx = ToolingExtensions.readStringExtension(ex, "index"); 4259 Extension exv = ToolingExtensions.getExtension(ex, "exValue"); 4260 if (exv != null) { 4261 Type value = exv.getValue(); 4262 if (index.equals(ndx) && value != null) 4263 return true; 4264 } 4265 } 4266 return false; 4267 } 4268 4269 4270 private boolean hasAnyExampleValues(StructureDefinition sd) { 4271 for (ElementDefinition ed : sd.getSnapshot().getElement()) 4272 if (ed.hasExample()) 4273 return true; 4274 return false; 4275 } 4276 4277 4278 public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException { 4279 sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy()); 4280 4281 if (sd.hasBaseDefinition()) { 4282 StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 4283 if (base == null) 4284 throw new FHIRException("Unable to find base definition for logical model: "+sd.getBaseDefinition()+" from "+sd.getUrl()); 4285 copyElements(sd, base.getSnapshot().getElement()); 4286 } 4287 copyElements(sd, sd.getDifferential().getElement()); 4288 } 4289 4290 4291 private void copyElements(StructureDefinition sd, List<ElementDefinition> list) { 4292 for (ElementDefinition ed : list) { 4293 if (ed.getPath().contains(".")) { 4294 ElementDefinition n = ed.copy(); 4295 n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1)); 4296 sd.getSnapshot().addElement(n); 4297 } 4298 } 4299 } 4300 4301 4302 public void cleanUpDifferential(StructureDefinition sd) { 4303 if (sd.getDifferential().getElement().size() > 1) 4304 cleanUpDifferential(sd, 1); 4305 } 4306 4307 private void cleanUpDifferential(StructureDefinition sd, int start) { 4308 int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.'); 4309 int c = start; 4310 int len = sd.getDifferential().getElement().size(); 4311 HashSet<String> paths = new HashSet<String>(); 4312 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) { 4313 ElementDefinition ed = sd.getDifferential().getElement().get(c); 4314 if (!paths.contains(ed.getPath())) { 4315 paths.add(ed.getPath()); 4316 int ic = c+1; 4317 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4318 ic++; 4319 ElementDefinition slicer = null; 4320 List<ElementDefinition> slices = new ArrayList<ElementDefinition>(); 4321 slices.add(ed); 4322 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) { 4323 ElementDefinition edi = sd.getDifferential().getElement().get(ic); 4324 if (ed.getPath().equals(edi.getPath())) { 4325 if (slicer == null) { 4326 slicer = new ElementDefinition(); 4327 slicer.setPath(edi.getPath()); 4328 slicer.getSlicing().setRules(SlicingRules.OPEN); 4329 sd.getDifferential().getElement().add(c, slicer); 4330 c++; 4331 ic++; 4332 } 4333 slices.add(edi); 4334 } 4335 ic++; 4336 while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 4337 ic++; 4338 } 4339 // now we're at the end, we're going to figure out the slicing discriminator 4340 if (slicer != null) 4341 determineSlicing(slicer, slices); 4342 } 4343 c++; 4344 if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) { 4345 cleanUpDifferential(sd, c); 4346 c++; 4347 while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 4348 c++; 4349 } 4350 } 4351 } 4352 4353 4354 private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) { 4355 // first, name them 4356 int i = 0; 4357 for (ElementDefinition ed : slices) { 4358 if (ed.hasUserData("slice-name")) { 4359 ed.setSliceName(ed.getUserString("slice-name")); 4360 } else { 4361 i++; 4362 ed.setSliceName("slice-"+Integer.toString(i)); 4363 } 4364 } 4365 // now, the hard bit, how are they differentiated? 4366 // right now, we hard code this... 4367 if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension")) 4368 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); 4369 else if (slicer.getPath().equals("DiagnosticReport.result")) 4370 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code"); 4371 else if (slicer.getPath().equals("Observation.related")) 4372 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code"); 4373 else if (slicer.getPath().equals("Bundle.entry")) 4374 slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile"); 4375 else 4376 throw new Error("No slicing for "+slicer.getPath()); 4377 } 4378 4379 public class SpanEntry { 4380 private List<SpanEntry> children = new ArrayList<SpanEntry>(); 4381 private boolean profile; 4382 private String id; 4383 private String name; 4384 private String resType; 4385 private String cardinality; 4386 private String description; 4387 private String profileLink; 4388 private String resLink; 4389 private String type; 4390 4391 public String getName() { 4392 return name; 4393 } 4394 public void setName(String name) { 4395 this.name = name; 4396 } 4397 public String getResType() { 4398 return resType; 4399 } 4400 public void setResType(String resType) { 4401 this.resType = resType; 4402 } 4403 public String getCardinality() { 4404 return cardinality; 4405 } 4406 public void setCardinality(String cardinality) { 4407 this.cardinality = cardinality; 4408 } 4409 public String getDescription() { 4410 return description; 4411 } 4412 public void setDescription(String description) { 4413 this.description = description; 4414 } 4415 public String getProfileLink() { 4416 return profileLink; 4417 } 4418 public void setProfileLink(String profileLink) { 4419 this.profileLink = profileLink; 4420 } 4421 public String getResLink() { 4422 return resLink; 4423 } 4424 public void setResLink(String resLink) { 4425 this.resLink = resLink; 4426 } 4427 public String getId() { 4428 return id; 4429 } 4430 public void setId(String id) { 4431 this.id = id; 4432 } 4433 public boolean isProfile() { 4434 return profile; 4435 } 4436 public void setProfile(boolean profile) { 4437 this.profile = profile; 4438 } 4439 public List<SpanEntry> getChildren() { 4440 return children; 4441 } 4442 public String getType() { 4443 return type; 4444 } 4445 public void setType(String type) { 4446 this.type = type; 4447 } 4448 4449 } 4450 4451 public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException { 4452 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true); 4453 gen.setTranslator(getTranslator()); 4454 TableModel model = initSpanningTable(gen, "", false, profile.getId()); 4455 Set<String> processed = new HashSet<String>(); 4456 SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix); 4457 4458 genSpanEntry(gen, model.getRows(), span); 4459 return gen.generate(model, "", 0, outputTracker); 4460 } 4461 4462 private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException { 4463 SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile); 4464 boolean wantProcess = !processed.contains(profile.getUrl()); 4465 processed.add(profile.getUrl()); 4466 if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 4467 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 4468 if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) { 4469 String card = getCardinality(ed, profile.getSnapshot().getElement()); 4470 if (!card.endsWith(".0")) { 4471 List<String> refProfiles = listReferenceProfiles(ed); 4472 if (refProfiles.size() > 0) { 4473 String uri = refProfiles.get(0); 4474 if (uri != null) { 4475 StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri); 4476 if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) { 4477 res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix)); 4478 } 4479 } 4480 } 4481 } 4482 } 4483 } 4484 } 4485 return res; 4486 } 4487 4488 4489 private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) { 4490 int min = ed.getMin(); 4491 int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax()); 4492 while (ed != null && ed.getPath().contains(".")) { 4493 ed = findParent(ed, list); 4494 if (ed.getMax().equals("0")) 4495 max = 0; 4496 else if (!ed.getMax().equals("1") && !ed.hasSlicing()) 4497 max = Integer.MAX_VALUE; 4498 if (ed.getMin() == 0) 4499 min = 0; 4500 } 4501 return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max)); 4502 } 4503 4504 4505 private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) { 4506 int i = list.indexOf(ed)-1; 4507 while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+".")) 4508 i--; 4509 if (i == -1) 4510 return null; 4511 else 4512 return list.get(i); 4513 } 4514 4515 4516 private List<String> listReferenceProfiles(ElementDefinition ed) { 4517 List<String> res = new ArrayList<String>(); 4518 for (TypeRefComponent tr : ed.getType()) { 4519 // code is null if we're dealing with "value" and profile is null if we just have Reference() 4520 if (tr.hasTarget() && tr.hasTargetProfile()) 4521 for (UriType u : tr.getTargetProfile()) 4522 res.add(u.getValue()); 4523 } 4524 return res; 4525 } 4526 4527 4528 private String nameForElement(ElementDefinition ed) { 4529 return ed.getPath().substring(ed.getPath().indexOf(".")+1); 4530 } 4531 4532 4533 private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException { 4534 SpanEntry res = new SpanEntry(); 4535 res.setName(name); 4536 res.setCardinality(cardinality); 4537 res.setProfileLink(profile.getUserString("path")); 4538 res.setResType(profile.getType()); 4539 StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType()); 4540 if (base != null) 4541 res.setResLink(base.getUserString("path")); 4542 res.setId(profile.getId()); 4543 res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT); 4544 StringBuilder b = new StringBuilder(); 4545 b.append(res.getResType()); 4546 boolean first = true; 4547 boolean open = false; 4548 if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) { 4549 res.setDescription(profile.getName()); 4550 for (ElementDefinition ed : profile.getSnapshot().getElement()) { 4551 if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) { 4552 if (first) { 4553 open = true; 4554 first = false; 4555 b.append("["); 4556 } else { 4557 b.append(", "); 4558 } 4559 b.append(tail(ed.getBase().getPath())); 4560 b.append("="); 4561 b.append(summarize(ed.getFixed())); 4562 } 4563 } 4564 if (open) 4565 b.append("]"); 4566 } else 4567 res.setDescription("Base FHIR "+profile.getName()); 4568 res.setType(b.toString()); 4569 return res ; 4570 } 4571 4572 4573 private String summarize(Type value) throws IOException { 4574 if (value instanceof Coding) 4575 return summarizeCoding((Coding) value); 4576 else if (value instanceof CodeableConcept) 4577 return summarizeCodeableConcept((CodeableConcept) value); 4578 else 4579 return buildJson(value); 4580 } 4581 4582 4583 private String summarizeCoding(Coding value) { 4584 String uri = value.getSystem(); 4585 String system = NarrativeGenerator.describeSystem(uri); 4586 if (Utilities.isURL(system)) { 4587 if (system.equals("http://cap.org/protocols")) 4588 system = "CAP Code"; 4589 } 4590 return system+" "+value.getCode(); 4591 } 4592 4593 4594 private String summarizeCodeableConcept(CodeableConcept value) { 4595 if (value.hasCoding()) 4596 return summarizeCoding(value.getCodingFirstRep()); 4597 else 4598 return value.getText(); 4599 } 4600 4601 4602 private boolean isKeyProperty(String path) { 4603 return Utilities.existsInList(path, "Observation.code"); 4604 } 4605 4606 4607 public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) { 4608 TableModel model = gen.new TableModel(id, false); 4609 4610 model.setDocoImg(prefix+"help16.png"); 4611 model.setDocoRef(prefix+"formats.html#table"); // todo: change to graph definition 4612 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0)); 4613 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)); 4614 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0)); 4615 model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0)); 4616 return model; 4617 } 4618 4619 private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException { 4620 Row row = gen.new Row(); 4621 rows.add(row); 4622 row.setAnchor(span.getId()); 4623 //row.setColor(..?); 4624 if (span.isProfile()) 4625 row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE); 4626 else 4627 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 4628 4629 row.getCells().add(gen.new Cell(null, null, span.getName(), null, null)); 4630 row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null)); 4631 row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null)); 4632 row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null)); 4633 4634 for (SpanEntry child : span.getChildren()) 4635 genSpanEntry(gen, row.getSubRows(), child); 4636 } 4637 4638 4639 public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) { 4640 if (discriminator.endsWith("@pattern")) 4641 return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 4642 if (discriminator.endsWith("@profile")) 4643 return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 4644 if (discriminator.endsWith("@type")) 4645 return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6)); 4646 if (discriminator.endsWith("@exists")) 4647 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 4648 if (isExists) 4649 return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 4650 return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator); 4651 } 4652 4653 4654 private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) { 4655 return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str); 4656 } 4657 4658 4659 public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException { 4660 switch (t.getType()) { 4661 case PROFILE: return t.getPath()+"/@profile"; 4662 case TYPE: return t.getPath()+"/@type"; 4663 case VALUE: return t.getPath(); 4664 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 4665 default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2"); 4666 } 4667 } 4668 4669 4670 public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) { 4671 String epath = url.substring(54); 4672 if (!epath.contains(".")) 4673 return null; 4674 String type = epath.substring(0, epath.indexOf(".")); 4675 StructureDefinition sd = context.fetchTypeDefinition(type); 4676 if (sd == null) 4677 return null; 4678 ElementDefinition ed = null; 4679 for (ElementDefinition t : sd.getSnapshot().getElement()) { 4680 if (t.getPath().equals(epath)) { 4681 ed = t; 4682 break; 4683 } 4684 } 4685 if (ed == null) 4686 return null; 4687 if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) { 4688 return null; 4689 } else { 4690 StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"); 4691 StructureDefinition ext = template.copy(); 4692 ext.setUrl(url); 4693 ext.setId("extension-"+epath); 4694 ext.setName("Extension-"+epath); 4695 ext.setTitle("Extension for r4 "+epath); 4696 ext.setStatus(sd.getStatus()); 4697 ext.setDate(sd.getDate()); 4698 ext.getContact().clear(); 4699 ext.getContact().addAll(sd.getContact()); 4700 ext.setFhirVersion(sd.getFhirVersion()); 4701 ext.setDescription(ed.getDefinition()); 4702 ext.getContext().clear(); 4703 ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf("."))); 4704 ext.getDifferential().getElement().clear(); 4705 ext.getSnapshot().getElement().get(3).setFixed(new UriType(url)); 4706 ext.getSnapshot().getElement().set(4, ed.copy()); 4707 ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary())); 4708 return ext; 4709 } 4710 4711 } 4712 4713 4714 public boolean isThrowException() { 4715 return exception; 4716 } 4717 4718 4719 public void setThrowException(boolean exception) { 4720 this.exception = exception; 4721 } 4722 4723 4724 public TerminologyServiceOptions getTerminologyServiceOptions() { 4725 return terminologyServiceOptions; 4726 } 4727 4728 4729 public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) { 4730 this.terminologyServiceOptions = terminologyServiceOptions; 4731 } 4732 4733 4734 public boolean isNewSlicingProcessing() { 4735 return newSlicingProcessing; 4736 } 4737 4738 4739 public void setNewSlicingProcessing(boolean newSlicingProcessing) { 4740 this.newSlicingProcessing = newSlicingProcessing; 4741 } 4742 4743 4744 public boolean isDebug() { 4745 return debug; 4746 } 4747 4748 4749 public void setDebug(boolean debug) { 4750 this.debug = debug; 4751 } 4752 4753 4754 4755 4756}