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