001package org.hl7.fhir.dstu2.utils; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032import java.io.IOException; 033import java.io.OutputStream; 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.Comparator; 037import java.util.HashSet; 038import java.util.List; 039import java.util.Set; 040 041import org.hl7.fhir.dstu2.formats.IParser; 042import org.hl7.fhir.dstu2.model.Base; 043import org.hl7.fhir.dstu2.model.BooleanType; 044import org.hl7.fhir.dstu2.model.Coding; 045import org.hl7.fhir.dstu2.model.Element; 046import org.hl7.fhir.dstu2.model.ElementDefinition; 047import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionBindingComponent; 048import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionConstraintComponent; 049import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionMappingComponent; 050import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionSlicingComponent; 051import org.hl7.fhir.dstu2.model.ElementDefinition.SlicingRules; 052import org.hl7.fhir.dstu2.model.ElementDefinition.TypeRefComponent; 053import org.hl7.fhir.dstu2.model.Enumerations.BindingStrength; 054import org.hl7.fhir.dstu2.model.IntegerType; 055import org.hl7.fhir.dstu2.model.PrimitiveType; 056import org.hl7.fhir.dstu2.model.Reference; 057import org.hl7.fhir.dstu2.model.Resource; 058import org.hl7.fhir.dstu2.model.StringType; 059import org.hl7.fhir.dstu2.model.StructureDefinition; 060import org.hl7.fhir.dstu2.model.StructureDefinition.StructureDefinitionDifferentialComponent; 061import org.hl7.fhir.dstu2.model.StructureDefinition.StructureDefinitionSnapshotComponent; 062import org.hl7.fhir.dstu2.model.Type; 063import org.hl7.fhir.dstu2.model.UriType; 064import org.hl7.fhir.dstu2.model.ValueSet; 065import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent; 066import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent; 067import org.hl7.fhir.dstu2.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 068import org.hl7.fhir.dstu2.utils.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution; 069import org.hl7.fhir.exceptions.DefinitionException; 070import org.hl7.fhir.exceptions.FHIRException; 071import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 072import org.hl7.fhir.utilities.Utilities; 073import org.hl7.fhir.utilities.i18n.RenderingI18nContext; 074import org.hl7.fhir.utilities.validation.ValidationMessage; 075import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 076import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 077import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 078import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 079import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 080import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 081import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 082import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMode; 083import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 084import org.hl7.fhir.utilities.xhtml.XhtmlNode; 085import org.hl7.fhir.utilities.xml.SchematronWriter; 086import org.hl7.fhir.utilities.xml.SchematronWriter.Rule; 087import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType; 088import org.hl7.fhir.utilities.xml.SchematronWriter.Section; 089 090/** 091 * This class provides a set of utility operations for working with Profiles. 092 * Key functionality: * getChildMap --? * getChildList * generateSnapshot: Given 093 * a base (snapshot) profile structure, and a differential profile, generate a 094 * new snapshot profile * generateExtensionsTable: generate the HTML for a 095 * hierarchical table presentation of the extensions * generateTable: generate 096 * the HTML for a hierarchical table presentation of a structure * summarise: 097 * describe the contents of a profile 098 * 099 * @author Grahame 100 * 101 */ 102public class ProfileUtilities { 103 104 public class ExtensionContext { 105 106 private ElementDefinition element; 107 private StructureDefinition defn; 108 109 public ExtensionContext(StructureDefinition ext, ElementDefinition ed) { 110 this.defn = ext; 111 this.element = ed; 112 } 113 114 public ElementDefinition getElement() { 115 return element; 116 } 117 118 public StructureDefinition getDefn() { 119 return defn; 120 } 121 122 public String getUrl() { 123 if (element == defn.getSnapshot().getElement().get(0)) 124 return defn.getUrl(); 125 else 126 return element.getName(); 127 } 128 129 public ElementDefinition getExtensionValueDefinition() { 130 int i = defn.getSnapshot().getElement().indexOf(element) + 1; 131 while (i < defn.getSnapshot().getElement().size()) { 132 ElementDefinition ed = defn.getSnapshot().getElement().get(i); 133 if (ed.getPath().equals(element.getPath())) 134 return null; 135 if (ed.getPath().startsWith(element.getPath() + ".value")) 136 return ed; 137 i++; 138 } 139 return null; 140 } 141 142 } 143 144 private final boolean ADD_REFERENCE_TO_TABLE = true; 145 146 private static final String ROW_COLOR_ERROR = "#ffcccc"; 147 private static final String ROW_COLOR_FATAL = "#ff9999"; 148 private static final String ROW_COLOR_WARNING = "#ffebcc"; 149 private static final String ROW_COLOR_HINT = "#ebf5ff"; 150 public static final int STATUS_OK = 0; 151 public static final int STATUS_HINT = 1; 152 public static final int STATUS_WARNING = 2; 153 public static final int STATUS_ERROR = 3; 154 public static final int STATUS_FATAL = 4; 155 156 private static final String DERIVATION_EQUALS = "derivation.equals"; 157 public static final String DERIVATION_POINTER = "derived.pointer"; 158 public static final String IS_DERIVED = "derived.fact"; 159 public static final String UD_ERROR_STATUS = "error-status"; 160 161 // note that ProfileUtilities are used re-entrantly internally, so nothing with 162 // process state can be here 163 private final IWorkerContext context; 164 private List<ValidationMessage> messages; 165 private List<String> snapshotStack = new ArrayList<String>(); 166 private ProfileKnowledgeProvider pkp; 167 168 public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) { 169 super(); 170 this.context = context; 171 this.messages = messages; 172 this.pkp = pkp; 173 } 174 175 private class UnusedTracker { 176 private boolean used; 177 } 178 179 public interface ProfileKnowledgeProvider { 180 public class BindingResolution { 181 public String display; 182 public String url; 183 } 184 185 boolean isDatatype(String typeSimple); 186 187 boolean isResource(String typeSimple); 188 189 boolean hasLinkFor(String typeSimple); 190 191 String getLinkFor(String typeSimple); 192 193 BindingResolution resolveBinding(ElementDefinitionBindingComponent binding); 194 195 String getLinkForProfile(StructureDefinition profile, String url); 196 } 197 198 /** 199 * Given a Structure, navigate to the element given by the path and return the 200 * direct children of that element 201 * 202 * @param structure The structure to navigate into 203 * @param path The path of the element within the structure to get the 204 * children for 205 * @return A Map containing the name of the element child (not the path) and the 206 * child itself (an Element) 207 * @throws DefinitionException 208 * @throws Exception 209 */ 210 public static List<ElementDefinition> getChildMap(StructureDefinition profile, String name, String path, 211 String nameReference) throws DefinitionException { 212 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 213 214 // if we have a name reference, we have to find it, and iterate it's children 215 if (nameReference != null) { 216 boolean found = false; 217 for (ElementDefinition e : profile.getSnapshot().getElement()) { 218 if (nameReference.equals(e.getName())) { 219 found = true; 220 path = e.getPath(); 221 } 222 } 223 if (!found) 224 throw new DefinitionException("Unable to resolve name reference " + nameReference + " at path " + path); 225 } 226 227 for (ElementDefinition e : profile.getSnapshot().getElement()) { 228 String p = e.getPath(); 229 230 if (path != null && !Utilities.noString(e.getNameReference()) && path.startsWith(p)) { 231 /* 232 * The path we are navigating to is on or below this element, but the element 233 * defers its definition to another named part of the structure. 234 */ 235 if (path.length() > p.length()) { 236 // The path navigates further into the referenced element, so go ahead along the 237 // path over there 238 return getChildMap(profile, name, e.getNameReference() + "." + path.substring(p.length() + 1), null); 239 } else { 240 // The path we are looking for is actually this element, but since it defers it 241 // definition, go get the referenced element 242 return getChildMap(profile, name, e.getNameReference(), null); 243 } 244 } else if (p.startsWith(path + ".")) { 245 // The path of the element is a child of the path we're looking for (i.e. the 246 // parent), 247 // so add this element to the result. 248 String tail = p.substring(path.length() + 1); 249 250 // Only add direct children, not any deeper paths 251 if (!tail.contains(".")) { 252 res.add(e); 253 } 254 } 255 } 256 257 return res; 258 } 259 260 public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) 261 throws DefinitionException { 262 return getChildMap(profile, element.getName(), element.getPath(), null); 263 } 264 265 /** 266 * Given a Structure, navigate to the element given by the path and return the 267 * direct children of that element 268 * 269 * @param structure The structure to navigate into 270 * @param path The path of the element within the structure to get the 271 * children for 272 * @return A List containing the element children (all of them are Elements) 273 */ 274 public static List<ElementDefinition> getChildList(StructureDefinition profile, String path) { 275 List<ElementDefinition> res = new ArrayList<ElementDefinition>(); 276 277 for (ElementDefinition e : profile.getSnapshot().getElement()) { 278 String p = e.getPath(); 279 280 if (!Utilities.noString(e.getNameReference()) && path.startsWith(p)) { 281 if (path.length() > p.length()) 282 return getChildList(profile, e.getNameReference() + "." + path.substring(p.length() + 1)); 283 else 284 return getChildList(profile, e.getNameReference()); 285 } else if (p.startsWith(path + ".") && !p.equals(path)) { 286 String tail = p.substring(path.length() + 1); 287 if (!tail.contains(".")) { 288 res.add(e); 289 } 290 } 291 292 } 293 294 return res; 295 } 296 297 public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) { 298 return getChildList(structure, element.getPath()); 299 } 300 301 /** 302 * Given a base (snapshot) profile structure, and a differential profile, 303 * generate a new snapshot profile 304 * 305 * @param base - the base structure on which the differential will 306 * be applied 307 * @param differential - the differential to apply to the base 308 * @param url - where the base has relative urls for profile 309 * references, these need to be converted to absolutes 310 * by prepending this URL 311 * @param trimDifferential - if this is true, then the snap short generator will 312 * remove any material in the element definitions that 313 * is not different to the base 314 * @return 315 * @throws FHIRException 316 * @throws DefinitionException 317 * @throws Exception 318 */ 319 public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String profileName) 320 throws DefinitionException, FHIRException { 321 if (base == null) 322 throw new DefinitionException("no base profile provided"); 323 if (derived == null) 324 throw new DefinitionException("no derived structure provided"); 325 326 if (snapshotStack.contains(derived.getUrl())) 327 throw new DefinitionException( 328 "Circular snapshot references detected; cannot generate snapshot (stack = " + snapshotStack.toString() + ")"); 329 snapshotStack.add(derived.getUrl()); 330 331// System.out.println("Generate Snapshot for "+derived.getUrl()); 332 333 derived.setSnapshot(new StructureDefinitionSnapshotComponent()); 334 335 // so we have two lists - the base list, and the differential list 336 // the differential list is only allowed to include things that are in the base 337 // list, but 338 // is allowed to include them multiple times - thereby slicing them 339 340 // our approach is to walk through the base list, and see whether the 341 // differential 342 // says anything about them. 343 int baseCursor = 0; 344 int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by 345 // longer paths 346 347 // we actually delegate the work to a subroutine so we can re-enter it with a 348 // different cursors 349 processPaths(derived.getSnapshot(), base.getSnapshot(), derived.getDifferential(), baseCursor, diffCursor, 350 base.getSnapshot().getElement().size() - 1, derived.getDifferential().getElement().size() - 1, url, 351 derived.getId(), null, false, base.getUrl(), null, false); 352 } 353 354 /** 355 * @param trimDifferential 356 * @throws DefinitionException, FHIRException 357 * @throws Exception 358 */ 359 private void processPaths(StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, 360 StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit, 361 int diffLimit, String url, String profileName, String contextPath, boolean trimDifferential, String contextName, 362 String resultPathBase, boolean slicingDone) throws DefinitionException, FHIRException { 363 364 // just repeat processing entries until we run out of our allowed scope (1st 365 // entry, the allowed scope is all the entries) 366 while (baseCursor <= baseLimit) { 367 // get the current focus of the base, and decide what to do 368 ElementDefinition currentBase = base.getElement().get(baseCursor); 369 String cpath = fixedPath(contextPath, currentBase.getPath()); 370 List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get 371 // a 372 // list 373 // of 374 // matching 375 // elements 376 // in 377 // scope 378 379 // in the simple case, source is not sliced. 380 if (!currentBase.hasSlicing()) { 381 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 382 // so we just copy it in 383 ElementDefinition outcome = updateURLs(url, currentBase.copy()); 384 outcome.setPath(fixedPath(contextPath, outcome.getPath())); 385 updateFromBase(outcome, currentBase); 386 markDerived(outcome); 387 if (resultPathBase == null) 388 resultPathBase = outcome.getPath(); 389 else if (!outcome.getPath().startsWith(resultPathBase)) 390 throw new DefinitionException("Adding wrong path"); 391 result.getElement().add(outcome); 392 baseCursor++; 393 } else if (diffMatches.size() == 1 && (!diffMatches.get(0).hasSlicing() || slicingDone)) {// one matching 394 // element in the 395 // differential 396 ElementDefinition template = null; 397 if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 398 && diffMatches.get(0).getType().get(0).hasProfile() 399 && !diffMatches.get(0).getType().get(0).getCode().equals("Reference")) { 400 String p = diffMatches.get(0).getType().get(0).getProfile().get(0).asStringValue(); 401 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p); 402 if (sd != null) { 403 if (!sd.hasSnapshot()) { 404 StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBase()); 405 if (sdb == null) 406 throw new DefinitionException("no base for " + sd.getBase()); 407 generateSnapshot(sdb, sd, sd.getUrl(), sd.getName()); 408 } 409 template = sd.getSnapshot().getElement().get(0).copy().setPath(currentBase.getPath()); 410 // temporary work around 411 if (!diffMatches.get(0).getType().get(0).getCode().equals("Extension")) { 412 template.setMin(currentBase.getMin()); 413 template.setMax(currentBase.getMax()); 414 } 415 } 416 } 417 if (template == null) 418 template = currentBase.copy(); 419 else 420 // some of what's in currentBase overrides template 421 template = overWriteWithCurrent(template, currentBase); 422 ElementDefinition outcome = updateURLs(url, template); 423 outcome.setPath(fixedPath(contextPath, outcome.getPath())); 424 updateFromBase(outcome, currentBase); 425 if (diffMatches.get(0).hasName()) 426 outcome.setName(diffMatches.get(0).getName()); 427 outcome.setSlicing(null); 428 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url); 429 if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 430 && !outcome.getType().get(0).getCode().equals("*")) // if the base profile allows multiple types, but the 431 // profile only allows one, rename it 432 outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length() - 3) 433 + Utilities.capitalize(outcome.getType().get(0).getCode())); 434 if (resultPathBase == null) 435 resultPathBase = outcome.getPath(); 436 else if (!outcome.getPath().startsWith(resultPathBase)) 437 throw new DefinitionException("Adding wrong path"); 438 result.getElement().add(outcome); 439 baseCursor++; 440 diffCursor = differential.getElement().indexOf(diffMatches.get(0)) + 1; 441 if (differential.getElement().size() > diffCursor && outcome.getPath().contains(".") 442 && isDataType(outcome.getType())) { // don't want to do this for the root, since that's base, and we're 443 // already processing it 444 if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), 445 diffMatches.get(0).getPath() + ".")) { 446 if (outcome.getType().size() > 1) 447 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 448 + differential.getElement().get(diffCursor).getPath() + ") and multiple types (" 449 + typeCode(outcome.getType()) + ") in profile " + profileName); 450 StructureDefinition dt = getProfileForDataType(outcome.getType().get(0)); 451 if (dt == null) 452 throw new DefinitionException(diffMatches.get(0).getPath() + " has children (" 453 + differential.getElement().get(diffCursor).getPath() + ") for type " + typeCode(outcome.getType()) 454 + " in profile " + profileName + ", but can't find type"); 455 contextName = dt.getUrl(); 456 int start = diffCursor; 457 while (differential.getElement().size() > diffCursor && pathStartsWith( 458 differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) 459 diffCursor++; 460 processPaths(result, dt.getSnapshot(), differential, 461 1 /* starting again on the data type, but skip the root */, start - 1, 462 dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, profileName + pathTail(diffMatches, 0), 463 diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false); 464 } 465 } 466 } else { 467 // ok, the differential slices the item. Let's check our pre-conditions to 468 // ensure that this is correct 469 if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0))) 470 // you can only slice an element that doesn't repeat if the sum total of your 471 // slices is limited to 1 472 // (but you might do that in order to split up constraints by type) 473 throw new DefinitionException("Attempt to a slice an element that does not repeat: " + currentBase.getPath() 474 + "/" + currentBase.getName() + " from " + contextName); 475 if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but 476 // hasn't defined it. this is an error 477 throw new DefinitionException("differential does not have a slice: " + currentBase.getPath()); 478 479 // well, if it passed those preconditions then we slice the dest. 480 // we're just going to accept the differential slicing at face value 481 ElementDefinition outcome = updateURLs(url, currentBase.copy()); 482 outcome.setPath(fixedPath(contextPath, outcome.getPath())); 483 updateFromBase(outcome, currentBase); 484 485 if (!diffMatches.get(0).hasSlicing()) 486 outcome.setSlicing(makeExtensionSlicing()); 487 else 488 outcome.setSlicing(diffMatches.get(0).getSlicing().copy()); 489 if (!outcome.getPath().startsWith(resultPathBase)) 490 throw new DefinitionException("Adding wrong path"); 491 result.getElement().add(outcome); 492 493 // differential - if the first one in the list has a name, we'll process it. 494 // Else we'll treat it as the base definition of the slice. 495 int start = 0; 496 if (!diffMatches.get(0).hasName()) { 497 updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url); 498 if (!outcome.hasType()) { 499 throw new DefinitionException("not done yet"); 500 } 501 start = 1; 502 } else 503 checkExtensionDoco(outcome); 504 505 // now, for each entry in the diff matches, we're going to process the base item 506 // our processing scope for base is all the children of the current path 507 int nbl = findEndOfElement(base, baseCursor); 508 int ndc = diffCursor; 509 int ndl = diffCursor; 510 for (int i = start; i < diffMatches.size(); i++) { 511 // our processing scope for the differential is the item in the list, and all 512 // the items before the next one in the list 513 ndc = differential.getElement().indexOf(diffMatches.get(i)); 514 ndl = findEndOfElement(differential, ndc); 515 // now we process the base scope repeatedly for each instance of the item in the 516 // differential list 517 processPaths(result, base, differential, baseCursor, ndc, nbl, ndl, url, 518 profileName + pathTail(diffMatches, i), contextPath, trimDifferential, contextName, resultPathBase, 519 true); 520 } 521 // ok, done with that - next in the base list 522 baseCursor = nbl + 1; 523 diffCursor = ndl + 1; 524 } 525 } else { 526 // the item is already sliced in the base profile. 527 // here's the rules 528 // 1. irrespective of whether the slicing is ordered or not, the definition 529 // order must be maintained 530 // 2. slice element names have to match. 531 // 3. new slices must be introduced at the end 532 // corallory: you can't re-slice existing slices. is that ok? 533 534 // we're going to need this: 535 String path = currentBase.getPath(); 536 ElementDefinition original = currentBase; 537 538 if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item 539 // copy across the currentbase, and all of it's children and siblings 540 while (baseCursor < base.getElement().size() 541 && base.getElement().get(baseCursor).getPath().startsWith(path)) { 542 ElementDefinition outcome = updateURLs(url, base.getElement().get(baseCursor).copy()); 543 if (!outcome.getPath().startsWith(resultPathBase)) 544 throw new DefinitionException("Adding wrong path"); 545 result.getElement().add(outcome); // so we just copy it in 546 baseCursor++; 547 } 548 } else { 549 // first - check that the slicing is ok 550 boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED; 551 int diffpos = 0; 552 boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension"); 553 if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything 554 // about slicing 555 if (!isExtension) 556 diffpos++; // if there's a slice on the first, we'll ignore any content it has 557 ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing(); 558 ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing(); 559 if (!orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement())) 560 throw new DefinitionException( 561 "Slicing rules on differential (" + summariseSlicing(dSlice) + ") do not match those on base (" 562 + summariseSlicing(bSlice) + ") - order @ " + path + " (" + contextName + ")"); 563 if (!discriiminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator())) 564 throw new DefinitionException( 565 "Slicing rules on differential (" + summariseSlicing(dSlice) + ") do not match those on base (" 566 + summariseSlicing(bSlice) + ") - discriminator @ " + path + " (" + contextName + ")"); 567 if (!ruleMatches(dSlice.getRules(), bSlice.getRules())) 568 throw new DefinitionException( 569 "Slicing rules on differential (" + summariseSlicing(dSlice) + ") do not match those on base (" 570 + summariseSlicing(bSlice) + ") - rule @ " + path + " (" + contextName + ")"); 571 } 572 ElementDefinition outcome = updateURLs(url, currentBase.copy()); 573 outcome.setPath(fixedPath(contextPath, outcome.getPath())); 574 updateFromBase(outcome, currentBase); 575 if (diffMatches.get(0).hasSlicing() && !isExtension) { 576 updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing()); 577 updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url); // if there's no slice, we 578 // don't want to update the 579 // unsliced description 580 } 581 if (diffMatches.get(0).hasSlicing() && !diffMatches.get(0).hasName()) 582 diffpos++; 583 584 result.getElement().add(outcome); 585 586 // now, we have two lists, base and diff. we're going to work through base, 587 // looking for matches in diff. 588 List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase); 589 for (ElementDefinition baseItem : baseMatches) { 590 baseCursor = base.getElement().indexOf(baseItem); 591 outcome = updateURLs(url, baseItem.copy()); 592 updateFromBase(outcome, currentBase); 593 outcome.setPath(fixedPath(contextPath, outcome.getPath())); 594 outcome.setSlicing(null); 595 if (!outcome.getPath().startsWith(resultPathBase)) 596 throw new DefinitionException("Adding wrong path"); 597 if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getName().equals(outcome.getName())) { 598 // if there's a diff, we update the outcome with diff 599 // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, 600 // closed, url); 601 // then process any children 602 int nbl = findEndOfElement(base, baseCursor); 603 int ndc = differential.getElement().indexOf(diffMatches.get(diffpos)); 604 int ndl = findEndOfElement(differential, ndc); 605 // now we process the base scope repeatedly for each instance of the item in the 606 // differential list 607 processPaths(result, base, differential, baseCursor, ndc, nbl, ndl, url, 608 profileName + pathTail(diffMatches, diffpos), contextPath, closed, contextName, resultPathBase, true); 609 // ok, done with that - now set the cursors for if this is the end 610 baseCursor = nbl + 1; 611 diffCursor = ndl + 1; 612 diffpos++; 613 } else { 614 result.getElement().add(outcome); 615 baseCursor++; 616 // just copy any children on the base 617 while (baseCursor < base.getElement().size() 618 && base.getElement().get(baseCursor).getPath().startsWith(path) 619 && !base.getElement().get(baseCursor).getPath().equals(path)) { 620 outcome = updateURLs(url, currentBase.copy()); 621 outcome.setPath(fixedPath(contextPath, outcome.getPath())); 622 if (!outcome.getPath().startsWith(resultPathBase)) 623 throw new DefinitionException("Adding wrong path"); 624 result.getElement().add(outcome); 625 baseCursor++; 626 } 627 } 628 } 629 // finally, we process any remaining entries in diff, which are new (and which 630 // are only allowed if the base wasn't closed 631 if (closed && diffpos < diffMatches.size()) 632 throw new DefinitionException( 633 "The base snapshot marks a slicing as closed, but the differential tries to extend it in " + profileName 634 + " at " + path + " (" + cpath + ")"); 635 while (diffpos < diffMatches.size()) { 636 ElementDefinition diffItem = diffMatches.get(diffpos); 637 for (ElementDefinition baseItem : baseMatches) 638 if (baseItem.getName().equals(diffItem.getName())) 639 throw new DefinitionException("Named items are out of order in the slice"); 640 outcome = updateURLs(url, original.copy()); 641 outcome.setPath(fixedPath(contextPath, outcome.getPath())); 642 updateFromBase(outcome, currentBase); 643 outcome.setSlicing(null); 644 if (!outcome.getPath().startsWith(resultPathBase)) 645 throw new DefinitionException("Adding wrong path"); 646 result.getElement().add(outcome); 647 updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url); 648 diffpos++; 649 } 650 } 651 } 652 } 653 } 654 655 private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) { 656 ElementDefinition res = profile.copy(); 657 if (usage.hasName()) 658 res.setName(usage.getName()); 659 if (usage.hasLabel()) 660 res.setLabel(usage.getLabel()); 661 for (Coding c : usage.getCode()) 662 res.addCode(c); 663 664 if (usage.hasDefinition()) 665 res.setDefinition(usage.getDefinition()); 666 if (usage.hasShort()) 667 res.setShort(usage.getShort()); 668 if (usage.hasComments()) 669 res.setComments(usage.getComments()); 670 if (usage.hasRequirements()) 671 res.setRequirements(usage.getRequirements()); 672 for (StringType c : usage.getAlias()) 673 res.addAlias(c.getValue()); 674 if (usage.hasMin()) 675 res.setMin(usage.getMin()); 676 if (usage.hasMax()) 677 res.setMax(usage.getMax()); 678 679 if (usage.hasFixed()) 680 res.setFixed(usage.getFixed()); 681 if (usage.hasPattern()) 682 res.setPattern(usage.getPattern()); 683 if (usage.hasExample()) 684 res.setExample(usage.getExample()); 685 if (usage.hasMinValue()) 686 res.setMinValue(usage.getMinValue()); 687 if (usage.hasMaxValue()) 688 res.setMaxValue(usage.getMaxValue()); 689 if (usage.hasMaxLength()) 690 res.setMaxLength(usage.getMaxLength()); 691 if (usage.hasMustSupport()) 692 res.setMustSupport(usage.getMustSupport()); 693 if (usage.hasBinding()) 694 res.setBinding(usage.getBinding().copy()); 695 for (ElementDefinitionConstraintComponent c : usage.getConstraint()) 696 res.addConstraint(c); 697 698 return res; 699 } 700 701 private boolean checkExtensionDoco(ElementDefinition base) { 702 // see task 3970. For an extension, there's no point copying across all the 703 // underlying definitional stuff 704 boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") 705 || base.getPath().endsWith(".modifierExtension"); 706 if (isExtension) { 707 base.setDefinition("An Extension"); 708 base.setShort("Extension"); 709 base.setCommentsElement(null); 710 base.setRequirementsElement(null); 711 base.getAlias().clear(); 712 base.getMapping().clear(); 713 } 714 return isExtension; 715 } 716 717 private String pathTail(List<ElementDefinition> diffMatches, int i) { 718 719 ElementDefinition d = diffMatches.get(i); 720 String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".") + 1) : d.getPath(); 721 return "." + s 722 + (d.hasType() && d.getType().get(0).hasProfile() 723 ? "[" + d.getType().get(0).getProfile().get(0).asStringValue() + "]" 724 : ""); 725 } 726 727 private void markDerived(ElementDefinition outcome) { 728 for (ElementDefinitionConstraintComponent inv : outcome.getConstraint()) 729 inv.setUserData(IS_DERIVED, true); 730 } 731 732 private String summariseSlicing(ElementDefinitionSlicingComponent slice) { 733 StringBuilder b = new StringBuilder(); 734 boolean first = true; 735 for (StringType d : slice.getDiscriminator()) { 736 if (first) 737 first = false; 738 else 739 b.append(", "); 740 b.append(d); 741 } 742 b.append("("); 743 if (slice.hasOrdered()) 744 b.append(slice.getOrderedElement().asStringValue()); 745 b.append("/"); 746 if (slice.hasRules()) 747 b.append(slice.getRules().toCode()); 748 b.append(")"); 749 if (slice.hasDescription()) { 750 b.append(" \""); 751 b.append(slice.getDescription()); 752 b.append("\""); 753 } 754 return b.toString(); 755 } 756 757 private void updateFromBase(ElementDefinition derived, ElementDefinition base) { 758 if (base.hasBase()) { 759 derived.getBase().setPath(base.getBase().getPath()); 760 derived.getBase().setMin(base.getBase().getMin()); 761 derived.getBase().setMax(base.getBase().getMax()); 762 } else { 763 derived.getBase().setPath(base.getPath()); 764 derived.getBase().setMin(base.getMin()); 765 derived.getBase().setMax(base.getMax()); 766 } 767 } 768 769 private boolean pathStartsWith(String p1, String p2) { 770 return p1.startsWith(p2); 771 } 772 773 private boolean pathMatches(String p1, String p2) { 774 return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length() - 3)) 775 && !p1.substring(p2.length() - 3).contains(".")); 776 } 777 778 private String fixedPath(String contextPath, String pathSimple) { 779 if (contextPath == null) 780 return pathSimple; 781 return contextPath + "." + pathSimple.substring(pathSimple.indexOf(".") + 1); 782 } 783 784 private StructureDefinition getProfileForDataType(TypeRefComponent type) { 785 StructureDefinition sd = null; 786 if (type.hasProfile()) 787 sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).asStringValue()); 788 if (sd == null) 789 sd = context.fetchTypeDefinition(type.getCode()); 790 if (sd == null) 791 System.out.println("XX: failed to find profle for type: " + type.getCode()); // debug GJM 792 return sd; 793 } 794 795 public static String typeCode(List<TypeRefComponent> types) { 796 StringBuilder b = new StringBuilder(); 797 boolean first = true; 798 for (TypeRefComponent type : types) { 799 if (first) 800 first = false; 801 else 802 b.append(", "); 803 b.append(type.getCode()); 804 if (type.hasProfile()) 805 b.append("{" + type.getProfile() + "}"); 806 } 807 return b.toString(); 808 } 809 810 private boolean isDataType(List<TypeRefComponent> types) { 811 if (types.isEmpty()) 812 return false; 813 for (TypeRefComponent type : types) { 814 String t = type.getCode(); 815 if (!isDataType(t) && !t.equals("Reference") && !t.equals("Narrative") && !t.equals("Extension") 816 && !t.equals("ElementDefinition") && !isPrimitive(t)) 817 return false; 818 } 819 return true; 820 } 821 822 /** 823 * Finds internal references in an Element's Binding and StructureDefinition 824 * references (in TypeRef) and bases them on the given url 825 * 826 * @param url - the base url to use to turn internal references into 827 * absolute references 828 * @param element - the Element to update 829 * @return - the updated Element 830 */ 831 private ElementDefinition updateURLs(String url, ElementDefinition element) { 832 if (element != null) { 833 ElementDefinition defn = element; 834 if (defn.hasBinding() && defn.getBinding().getValueSet() instanceof Reference 835 && ((Reference) defn.getBinding().getValueSet()).getReference().startsWith("#")) 836 ((Reference) defn.getBinding().getValueSet()) 837 .setReference(url + ((Reference) defn.getBinding().getValueSet()).getReference()); 838 for (TypeRefComponent t : defn.getType()) { 839 for (UriType tp : t.getProfile()) { 840 if (tp.getValue().startsWith("#")) 841 tp.setValue(url + t.getProfile()); 842 } 843 } 844 } 845 return element; 846 } 847 848 private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) { 849 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 850 String path = current.getPath(); 851 int cursor = list.indexOf(current) + 1; 852 while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) { 853 if (pathMatches(list.get(cursor).getPath(), path)) 854 result.add(list.get(cursor)); 855 cursor++; 856 } 857 return result; 858 } 859 860 private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) { 861 if (src.hasOrderedElement()) 862 dst.setOrderedElement(src.getOrderedElement().copy()); 863 if (src.hasDiscriminator()) 864 dst.getDiscriminator().addAll(src.getDiscriminator()); 865 if (src.hasRulesElement()) 866 dst.setRulesElement(src.getRulesElement().copy()); 867 } 868 869 private boolean orderMatches(BooleanType diff, BooleanType base) { 870 return (diff == null) || (base == null) || (diff.getValue() == base.getValue()); 871 } 872 873 private boolean discriiminatorMatches(List<StringType> diff, List<StringType> base) { 874 if (diff.isEmpty() || base.isEmpty()) 875 return true; 876 if (diff.size() != base.size()) 877 return false; 878 for (int i = 0; i < diff.size(); i++) 879 if (!diff.get(i).getValue().equals(base.get(i).getValue())) 880 return false; 881 return true; 882 } 883 884 private boolean ruleMatches(SlicingRules diff, SlicingRules base) { 885 return (diff == null) || (base == null) || (diff == base) || (diff == SlicingRules.OPEN) 886 || ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED)); 887 } 888 889 private boolean isSlicedToOneOnly(ElementDefinition e) { 890 return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); 891 } 892 893 private ElementDefinitionSlicingComponent makeExtensionSlicing() { 894 ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); 895 slice.addDiscriminator("url"); 896 slice.setOrdered(false); 897 slice.setRules(SlicingRules.OPEN); 898 return slice; 899 } 900 901 private boolean isExtension(ElementDefinition currentBase) { 902 return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension"); 903 } 904 905 private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, 906 int start, int end, String profileName) { 907 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 908 for (int i = start; i <= end; i++) { 909 String statedPath = context.getElement().get(i).getPath(); 910 if (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 911 && statedPath.substring(0, path.length() - 3).equals(path.substring(0, path.length() - 3)) 912 && !statedPath.substring(path.length()).contains("."))) { 913 result.add(context.getElement().get(i)); 914 } else if (result.isEmpty()) { 915// System.out.println("ignoring "+statedPath+" in differential of "+profileName); 916 } 917 } 918 return result; 919 } 920 921 private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) { 922 int result = cursor; 923 String path = context.getElement().get(cursor).getPath() + "."; 924 while (result < context.getElement().size() - 1 && context.getElement().get(result + 1).getPath().startsWith(path)) 925 result++; 926 return result; 927 } 928 929 private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) { 930 int result = cursor; 931 String path = context.getElement().get(cursor).getPath() + "."; 932 while (result < context.getElement().size() - 1 && context.getElement().get(result + 1).getPath().startsWith(path)) 933 result++; 934 return result; 935 } 936 937 private boolean unbounded(ElementDefinition definition) { 938 StringType max = definition.getMaxElement(); 939 if (max == null) 940 return false; // this is not valid 941 if (max.getValue().equals("1")) 942 return false; 943 if (max.getValue().equals("0")) 944 return false; 945 return true; 946 } 947 948 private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, 949 boolean trimDifferential, String purl) throws DefinitionException, FHIRException { 950 // we start with a clone of the base profile ('dest') and we copy from the 951 // profile ('source') 952 // over the top for anything the source has 953 ElementDefinition base = dest; 954 ElementDefinition derived = source; 955 derived.setUserData(DERIVATION_POINTER, base); 956 957 if (derived != null) { 958 boolean isExtension = checkExtensionDoco(base); 959 960 if (derived.hasShortElement()) { 961 if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false)) 962 base.setShortElement(derived.getShortElement().copy()); 963 else if (trimDifferential) 964 derived.setShortElement(null); 965 else if (derived.hasShortElement()) 966 derived.getShortElement().setUserData(DERIVATION_EQUALS, true); 967 } 968 969 if (derived.hasDefinitionElement()) { 970 if (derived.getDefinition().startsWith("...")) 971 base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition())); 972 else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) 973 base.setDefinitionElement(derived.getDefinitionElement().copy()); 974 else if (trimDifferential) 975 derived.setDefinitionElement(null); 976 else if (derived.hasDefinitionElement()) 977 derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true); 978 } 979 980 if (derived.hasCommentsElement()) { 981 if (derived.getComments().startsWith("...")) 982 base.setComments(Utilities.appendDerivedTextToBase(base.getComments(), derived.getComments())); 983 else if (!Base.compareDeep(derived.getCommentsElement(), base.getCommentsElement(), false)) 984 base.setCommentsElement(derived.getCommentsElement().copy()); 985 else if (trimDifferential) 986 base.setCommentsElement(derived.getCommentsElement().copy()); 987 else if (derived.hasCommentsElement()) 988 derived.getCommentsElement().setUserData(DERIVATION_EQUALS, true); 989 } 990 991 if (derived.hasLabelElement()) { 992 if (derived.getLabel().startsWith("...")) 993 base.setLabel(Utilities.appendDerivedTextToBase(base.getLabel(), derived.getLabel())); 994 else if (!Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false)) 995 base.setLabelElement(derived.getLabelElement().copy()); 996 else if (trimDifferential) 997 base.setLabelElement(derived.getLabelElement().copy()); 998 else if (derived.hasLabelElement()) 999 derived.getLabelElement().setUserData(DERIVATION_EQUALS, true); 1000 } 1001 1002 if (derived.hasRequirementsElement()) { 1003 if (derived.getRequirements().startsWith("...")) 1004 base.setRequirements(Utilities.appendDerivedTextToBase(base.getRequirements(), derived.getRequirements())); 1005 else if (!Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false)) 1006 base.setRequirementsElement(derived.getRequirementsElement().copy()); 1007 else if (trimDifferential) 1008 base.setRequirementsElement(derived.getRequirementsElement().copy()); 1009 else if (derived.hasRequirementsElement()) 1010 derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true); 1011 } 1012 // sdf-9 1013 if (derived.hasRequirements() && !base.getPath().contains(".")) 1014 derived.setRequirements(null); 1015 if (base.hasRequirements() && !base.getPath().contains(".")) 1016 base.setRequirements(null); 1017 1018 if (derived.hasAlias()) { 1019 if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false)) 1020 for (StringType s : derived.getAlias()) { 1021 if (!base.hasAlias(s.getValue())) 1022 base.getAlias().add(s.copy()); 1023 } 1024 else if (trimDifferential) 1025 derived.getAlias().clear(); 1026 else 1027 for (StringType t : derived.getAlias()) 1028 t.setUserData(DERIVATION_EQUALS, true); 1029 } 1030 1031 if (derived.hasMinElement()) { 1032 if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { 1033 if (derived.getMin() < base.getMin()) 1034 messages 1035 .add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, 1036 pn + "." + derived.getPath(), "Derived min (" + Integer.toString(derived.getMin()) 1037 + ") cannot be less than base min (" + Integer.toString(base.getMin()) + ")", 1038 IssueSeverity.ERROR)); 1039 base.setMinElement(derived.getMinElement().copy()); 1040 } else if (trimDifferential) 1041 derived.setMinElement(null); 1042 else 1043 derived.getMinElement().setUserData(DERIVATION_EQUALS, true); 1044 } 1045 1046 if (derived.hasMaxElement()) { 1047 if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { 1048 if (isLargerMax(derived.getMax(), base.getMax())) 1049 messages.add( 1050 new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn + "." + derived.getPath(), 1051 "Derived max (" + derived.getMax() + ") cannot be greater than base max (" + base.getMax() + ")", 1052 IssueSeverity.ERROR)); 1053 base.setMaxElement(derived.getMaxElement().copy()); 1054 } else if (trimDifferential) 1055 derived.setMaxElement(null); 1056 else 1057 derived.getMaxElement().setUserData(DERIVATION_EQUALS, true); 1058 } 1059 1060 if (derived.hasFixed()) { 1061 if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) { 1062 base.setFixed(derived.getFixed().copy()); 1063 } else if (trimDifferential) 1064 derived.setFixed(null); 1065 else 1066 derived.getFixed().setUserData(DERIVATION_EQUALS, true); 1067 } 1068 1069 if (derived.hasPattern()) { 1070 if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) { 1071 base.setPattern(derived.getPattern().copy()); 1072 } else if (trimDifferential) 1073 derived.setPattern(null); 1074 else 1075 derived.getPattern().setUserData(DERIVATION_EQUALS, true); 1076 } 1077 1078 if (derived.hasExample()) { 1079 if (!Base.compareDeep(derived.getExample(), base.getExample(), false)) 1080 base.setExample(derived.getExample().copy()); 1081 else if (trimDifferential) 1082 derived.setExample(null); 1083 else 1084 derived.getExample().setUserData(DERIVATION_EQUALS, true); 1085 } 1086 1087 if (derived.hasMaxLengthElement()) { 1088 if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false)) 1089 base.setMaxLengthElement(derived.getMaxLengthElement().copy()); 1090 else if (trimDifferential) 1091 derived.setMaxLengthElement(null); 1092 else 1093 derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true); 1094 } 1095 1096 if (derived.hasMaxValue()) { 1097 if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false)) 1098 base.setMaxValue(derived.getMaxValue().copy()); 1099 else if (trimDifferential) 1100 derived.setMaxValue(null); 1101 else 1102 derived.getMaxValue().setUserData(DERIVATION_EQUALS, true); 1103 } 1104 1105 if (derived.hasMinValue()) { 1106 if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false)) 1107 base.setMinValue(derived.getMinValue().copy()); 1108 else if (trimDifferential) 1109 derived.setMinValue(null); 1110 else 1111 derived.getMinValue().setUserData(DERIVATION_EQUALS, true); 1112 } 1113 1114 // todo: what to do about conditions? 1115 // condition : id 0..* 1116 1117 if (derived.hasMustSupportElement()) { 1118 if (!Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false)) 1119 base.setMustSupportElement(derived.getMustSupportElement().copy()); 1120 else if (trimDifferential) 1121 derived.setMustSupportElement(null); 1122 else 1123 derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true); 1124 } 1125 1126 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 1127 // but extensions can change isModifier 1128 if (isExtension) { 1129 if (!Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)) 1130 base.setIsModifierElement(derived.getIsModifierElement().copy()); 1131 else if (trimDifferential) 1132 derived.setIsModifierElement(null); 1133 else 1134 derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true); 1135 } 1136 1137 if (derived.hasBinding()) { 1138 if (!Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 1139 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED 1140 && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 1141 messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, 1142 pn + "." + derived.getPath(), "illegal attempt to change a binding from " 1143 + base.getBinding().getStrength().toCode() + " to " + derived.getBinding().getStrength().toCode(), 1144 IssueSeverity.ERROR)); 1145// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 1146 else if (base.hasBinding() && derived.hasBinding() 1147 && base.getBinding().getStrength() == BindingStrength.REQUIRED) { 1148 ValueSetExpansionOutcome expBase = context.expandVS( 1149 context.fetchResource(ValueSet.class, base.getBinding().getValueSetReference().getReference()), true); 1150 ValueSetExpansionOutcome expDerived = context.expandVS( 1151 context.fetchResource(ValueSet.class, derived.getBinding().getValueSetReference().getReference()), 1152 true); 1153 if (expBase.getValueset() == null) 1154 messages 1155 .add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn + "." + base.getPath(), 1156 "Binding " + base.getBinding().getValueSetReference().getReference() + " could not be expanded", 1157 IssueSeverity.WARNING)); 1158 else if (expDerived.getValueset() == null) 1159 messages 1160 .add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, 1161 pn + "." + derived.getPath(), "Binding " 1162 + derived.getBinding().getValueSetReference().getReference() + " could not be expanded", 1163 IssueSeverity.WARNING)); 1164 else if (!isSubset(expBase.getValueset(), expDerived.getValueset())) 1165 messages.add( 1166 new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn + "." + derived.getPath(), 1167 "Binding " + derived.getBinding().getValueSetReference().getReference() 1168 + " is not a subset of binding " + base.getBinding().getValueSetReference().getReference(), 1169 IssueSeverity.ERROR)); 1170 } 1171 base.setBinding(derived.getBinding().copy()); 1172 } else if (trimDifferential) 1173 derived.setBinding(null); 1174 else 1175 derived.getBinding().setUserData(DERIVATION_EQUALS, true); 1176 } // else if (base.hasBinding() && doesn't have bindable type ) 1177 // base 1178 1179 if (derived.hasIsSummaryElement()) { 1180 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) 1181 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 1182 else if (trimDifferential) 1183 derived.setIsSummaryElement(null); 1184 else 1185 derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true); 1186 } 1187 1188 if (derived.hasType()) { 1189 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 1190 if (base.hasType()) { 1191 for (TypeRefComponent ts : derived.getType()) { 1192 boolean ok = false; 1193 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1194 for (TypeRefComponent td : base.getType()) { 1195 b.append(td.getCode()); 1196 if (td.hasCode() && (td.getCode().equals(ts.getCode()) || td.getCode().equals("Extension") 1197 || td.getCode().equals("Element") || td.getCode().equals("*") || ((td.getCode().equals("Resource") 1198 || (td.getCode().equals("DomainResource")) && pkp.isResource(ts.getCode()))))) 1199 ok = true; 1200 } 1201 if (!ok) 1202 throw new DefinitionException("StructureDefinition " + pn + " at " + derived.getPath() 1203 + ": illegal constrained type " + ts.getCode() + " from " + b.toString()); 1204 } 1205 } 1206 base.getType().clear(); 1207 for (TypeRefComponent t : derived.getType()) { 1208 TypeRefComponent tt = t.copy(); 1209// tt.setUserData(DERIVATION_EQUALS, true); 1210 base.getType().add(tt); 1211 } 1212 } else if (trimDifferential) 1213 derived.getType().clear(); 1214 else 1215 for (TypeRefComponent t : derived.getType()) 1216 t.setUserData(DERIVATION_EQUALS, true); 1217 } 1218 1219 if (derived.hasMapping()) { 1220 // todo: mappings are not cumulative - one replaces another 1221 if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) { 1222 for (ElementDefinitionMappingComponent s : derived.getMapping()) { 1223 boolean found = false; 1224 for (ElementDefinitionMappingComponent d : base.getMapping()) { 1225 found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap())); 1226 } 1227 if (!found) 1228 base.getMapping().add(s); 1229 } 1230 } else if (trimDifferential) 1231 derived.getMapping().clear(); 1232 else 1233 for (ElementDefinitionMappingComponent t : derived.getMapping()) 1234 t.setUserData(DERIVATION_EQUALS, true); 1235 } 1236 1237 // todo: constraints are cumulative. there is no replacing 1238 for (ElementDefinitionConstraintComponent s : base.getConstraint()) 1239 s.setUserData(IS_DERIVED, true); 1240 if (derived.hasConstraint()) { 1241 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 1242 base.getConstraint().add(s.copy()); 1243 } 1244 } 1245 } 1246 } 1247 1248 private boolean isLargerMax(String derived, String base) { 1249 if ("*".equals(base)) 1250 return false; 1251 if ("*".equals(derived)) 1252 return true; 1253 return Integer.parseInt(derived) > Integer.parseInt(base); 1254 } 1255 1256 private boolean isSubset(ValueSet expBase, ValueSet expDerived) { 1257 return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion()); 1258 } 1259 1260 private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, 1261 ValueSetExpansionComponent expansion) { 1262 for (ValueSetExpansionContainsComponent cc : contains) { 1263 if (!inExpansion(cc, expansion.getContains())) 1264 return false; 1265 if (!codesInExpansion(cc.getContains(), expansion)) 1266 return false; 1267 } 1268 return true; 1269 } 1270 1271 private boolean inExpansion(ValueSetExpansionContainsComponent cc, 1272 List<ValueSetExpansionContainsComponent> contains) { 1273 for (ValueSetExpansionContainsComponent cc1 : contains) { 1274 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) 1275 return true; 1276 if (inExpansion(cc, cc1.getContains())) 1277 return true; 1278 } 1279 return false; 1280 } 1281 1282 public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, 1283 boolean inlineGraphics, boolean full, String corePath, Set<String> outputTracker) 1284 throws IOException, FHIRException { 1285 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics); 1286 TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false, TableGenerationMode.XML); 1287 1288 boolean deep = false; 1289 boolean vdeep = false; 1290 for (ElementDefinition eld : ed.getSnapshot().getElement()) { 1291 deep = deep || eld.getPath().contains("Extension.extension."); 1292 vdeep = vdeep || eld.getPath().contains("Extension.extension.extension."); 1293 } 1294 Row r = gen.new Row(); 1295 model.getRows().add(r); 1296 r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile + "-definitions.html#extension." + ed.getName(), 1297 ed.getSnapshot().getElement().get(0).getIsModifier() ? "modifierExtension" : "extension", null, null)); 1298 r.getCells().add(gen.new Cell()); 1299 r.getCells().add(gen.new Cell(null, null, 1300 describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null)); 1301 1302 if (full || vdeep) { 1303 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 1304 1305 r.setIcon(deep ? "icon_extension_complex.png" : "icon_extension_simple.png", 1306 deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX 1307 : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1308 List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), 1309 ed.getSnapshot().getElement().get(0)); 1310 for (ElementDefinition child : children) 1311 if (!child.getPath().endsWith(".id")) 1312 genElement(defFile == null ? "" : defFile + "-definitions.html#extension.", gen, r.getSubRows(), child, 1313 ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath); 1314 } else if (deep) { 1315 List<ElementDefinition> children = new ArrayList<ElementDefinition>(); 1316 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 1317 if (ted.getPath().equals("Extension.extension")) 1318 children.add(ted); 1319 } 1320 1321 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 1322 r.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 1323 1324 for (ElementDefinition c : children) { 1325 ElementDefinition ved = getValueFor(ed, c); 1326 ElementDefinition ued = getUrlFor(ed, c); 1327 if (ved != null && ued != null) { 1328 Row r1 = gen.new Row(); 1329 r.getSubRows().add(r1); 1330 r1.getCells() 1331 .add(gen.new Cell(null, defFile == null ? "" : defFile + "-definitions.html#extension." + ed.getName(), 1332 ((UriType) ued.getFixed()).getValue(), null, null)); 1333 r1.getCells().add(gen.new Cell()); 1334 r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null)); 1335 genTypes(gen, r1, ved, defFile, ed, corePath); 1336 r1.getCells().add(gen.new Cell(null, null, c.getDefinition(), null, null)); 1337 r1.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1338 } 1339 } 1340 } else { 1341 ElementDefinition ved = null; 1342 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 1343 if (ted.getPath().startsWith("Extension.value")) 1344 ved = ted; 1345 } 1346 1347 genTypes(gen, r, ved, defFile, ed, corePath); 1348 1349 r.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1350 } 1351 Cell c = gen.new Cell("", "", "URL = " + ed.getUrl(), null, null); 1352 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, ed.getName() + ": " + ed.getDescription(), null)); 1353 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null)); 1354 r.getCells().add(c); 1355 1356 return gen.generate(model, corePath, 0, outputTracker); 1357 } 1358 1359 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 1360 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 1361 while (i < ed.getSnapshot().getElement().size() 1362 && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".")) { 1363 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath() + ".url")) 1364 return ed.getSnapshot().getElement().get(i); 1365 i++; 1366 } 1367 return null; 1368 } 1369 1370 private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) { 1371 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 1372 while (i < ed.getSnapshot().getElement().size() 1373 && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".")) { 1374 if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".value")) 1375 return ed.getSnapshot().getElement().get(i); 1376 i++; 1377 } 1378 return null; 1379 } 1380 1381 private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, 1382 StructureDefinition profile, String corePath) { 1383 Cell c = gen.new Cell(); 1384 r.getCells().add(c); 1385 List<TypeRefComponent> types = e.getType(); 1386 if (!e.hasType()) { 1387 if (e.hasNameReference()) { 1388 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), e.getNameReference()); 1389 if (ed == null) 1390 c.getPieces().add(gen.new Piece(null, "Unknown reference to " + e.getNameReference(), null)); 1391 else 1392 c.getPieces().add(gen.new Piece("#" + ed.getPath(), "See " + ed.getPath(), null)); 1393 return c; 1394 } else { 1395 ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER); 1396 if (d != null && d.hasType()) { 1397 types = new ArrayList<ElementDefinition.TypeRefComponent>(); 1398 for (TypeRefComponent tr : d.getType()) { 1399 TypeRefComponent tt = tr.copy(); 1400 tt.setUserData(DERIVATION_EQUALS, true); 1401 types.add(tt); 1402 } 1403 } else 1404 return c; 1405 } 1406 } 1407 1408 boolean first = true; 1409 Element source = types.get(0); // either all types are the same, or we don't consider any of them the same 1410 1411 boolean allReference = ADD_REFERENCE_TO_TABLE && !types.isEmpty(); 1412 for (TypeRefComponent t : types) { 1413 if (!(t.getCode().equals("Reference") && t.hasProfile())) 1414 allReference = false; 1415 } 1416 if (allReference) { 1417 c.getPieces().add(gen.new Piece(corePath + "references.html", "Reference", null)); 1418 c.getPieces().add(gen.new Piece(null, "(", null)); 1419 } 1420 TypeRefComponent tl = null; 1421 for (TypeRefComponent t : types) { 1422 if (first) 1423 first = false; 1424 else if (allReference) 1425 c.addPiece(checkForNoChange(tl, gen.new Piece(null, " | ", null))); 1426 else 1427 c.addPiece(checkForNoChange(tl, gen.new Piece(null, ", ", null))); 1428 tl = t; 1429 if (t.getCode().equals("Reference") || (t.getCode().equals("Resource") && t.hasProfile())) { 1430 if (ADD_REFERENCE_TO_TABLE && !allReference) { 1431 c.getPieces().add(gen.new Piece(corePath + "references.html", "Reference", null)); 1432 c.getPieces().add(gen.new Piece(null, "(", null)); 1433 } 1434 if (t.hasProfile() && t.getProfile().get(0).getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 1435 StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue()); 1436 if (sd != null) { 1437 String disp = sd.hasDisplay() ? sd.getDisplay() : sd.getName(); 1438 c.addPiece(checkForNoChange(t, gen.new Piece(corePath + sd.getUserString("path"), disp, null))); 1439 } else { 1440 String rn = t.getProfile().get(0).getValue().substring(40); 1441 c.addPiece(checkForNoChange(t, gen.new Piece(corePath + pkp.getLinkFor(rn), rn, null))); 1442 } 1443 } else if (t.getProfile().size() == 0) { 1444 c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null))); 1445 } else if (t.getProfile().get(0).getValue().startsWith("#")) 1446 c.addPiece(checkForNoChange(t, 1447 gen.new Piece(corePath + profileBaseFileName + "." 1448 + t.getProfile().get(0).getValue().substring(1).toLowerCase() + ".html", 1449 t.getProfile().get(0).getValue(), null))); 1450 else 1451 c.addPiece(checkForNoChange(t, 1452 gen.new Piece(corePath + t.getProfile().get(0).getValue(), t.getProfile().get(0).getValue(), null))); 1453 if (ADD_REFERENCE_TO_TABLE && !allReference) { 1454 c.getPieces().add(gen.new Piece(null, ")", null)); 1455 } 1456 } else if (t.hasProfile()) { // a profiled type 1457 String ref; 1458 ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue()); 1459 if (ref != null) { 1460 String[] parts = ref.split("\\|"); 1461 c.addPiece(checkForNoChange(t, gen.new Piece(corePath + parts[0], parts[1], t.getCode()))); 1462 } else 1463 c.addPiece(checkForNoChange(t, gen.new Piece(corePath + ref, t.getCode(), null))); 1464 } else if (pkp.hasLinkFor(t.getCode())) { 1465 c.addPiece(checkForNoChange(t, gen.new Piece(corePath + pkp.getLinkFor(t.getCode()), t.getCode(), null))); 1466 } else 1467 c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null))); 1468 } 1469 if (allReference) { 1470 c.getPieces().add(gen.new Piece(null, ")", null)); 1471 } 1472 return c; 1473 } 1474 1475 private ElementDefinition getElementByName(List<ElementDefinition> elements, String nameReference) { 1476 for (ElementDefinition ed : elements) 1477 if (ed.hasName() && ed.getName().equals(nameReference)) 1478 return ed; 1479 return null; 1480 } 1481 1482 public static String describeExtensionContext(StructureDefinition ext) { 1483 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1484 for (StringType t : ext.getContext()) 1485 b.append(t.getValue()); 1486 if (!ext.hasContextType()) 1487 throw new Error("no context type on " + ext.getUrl()); 1488 switch (ext.getContextType()) { 1489 case DATATYPE: 1490 return "Use on data type: " + b.toString(); 1491 case EXTENSION: 1492 return "Use on extension: " + b.toString(); 1493 case RESOURCE: 1494 return "Use on element: " + b.toString(); 1495 case MAPPING: 1496 return "Use where element has mapping: " + b.toString(); 1497 default: 1498 return "??"; 1499 } 1500 } 1501 1502 private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) { 1503 IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 1504 StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 1505 if (min.isEmpty() && fallback != null) 1506 min = fallback.getMinElement(); 1507 if (max.isEmpty() && fallback != null) 1508 max = fallback.getMaxElement(); 1509 1510 tracker.used = !max.isEmpty() && !max.getValue().equals("0"); 1511 1512 if (min.isEmpty() && max.isEmpty()) 1513 return null; 1514 else 1515 return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue()); 1516 } 1517 1518 private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, 1519 UnusedTracker tracker, ElementDefinition fallback) { 1520 IntegerType min = !hasDef ? new IntegerType() 1521 : definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 1522 StringType max = !hasDef ? new StringType() 1523 : definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 1524 if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 1525 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 1526 min = base.getMinElement().copy(); 1527 min.setUserData(DERIVATION_EQUALS, true); 1528 } 1529 if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 1530 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 1531 max = base.getMaxElement().copy(); 1532 max.setUserData(DERIVATION_EQUALS, true); 1533 } 1534 if (min.isEmpty() && fallback != null) 1535 min = fallback.getMinElement(); 1536 if (max.isEmpty() && fallback != null) 1537 max = fallback.getMaxElement(); 1538 1539 if (!max.isEmpty()) 1540 tracker.used = !max.getValue().equals("0"); 1541 1542 Cell cell = gen.new Cell(null, null, null, null, null); 1543 row.getCells().add(cell); 1544 if (!min.isEmpty() || !max.isEmpty()) { 1545 cell.addPiece( 1546 checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null))); 1547 cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null))); 1548 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null))); 1549 } 1550 } 1551 1552 private Piece checkForNoChange(Element source, Piece piece) { 1553 if (source.hasUserData(DERIVATION_EQUALS)) { 1554 piece.addStyle("opacity: 0.4"); 1555 } 1556 return piece; 1557 } 1558 1559 private Piece checkForNoChange(Element src1, Element src2, Piece piece) { 1560 if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) { 1561 piece.addStyle("opacity: 0.5"); 1562 } 1563 return piece; 1564 } 1565 1566 public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, 1567 boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, Set<String> outputTracker) 1568 throws IOException, FHIRException { 1569 assert (diff != snapshot);// check it's ok to get rid of one of these 1570 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics); 1571 TableModel model = gen.initNormalTable(corePath, false, true, profile.getId() + (diff ? "d" : "s"), false, 1572 TableGenerationMode.XML); 1573 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 1574 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 1575 profiles.add(profile); 1576 genElement(defFile == null ? null : defFile + "#" + profile.getId() + ".", gen, model.getRows(), list.get(0), list, 1577 profiles, diff, profileBaseFileName, null, snapshot, corePath); 1578 return gen.generate(model, corePath, 0, outputTracker); 1579 } 1580 1581 private void genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, 1582 List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, 1583 Boolean extensions, boolean snapshot, String corePath) throws IOException { 1584 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size() - 1); 1585 String s = tail(element.getPath()); 1586 List<ElementDefinition> children = getChildren(all, element); 1587 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 1588 if (!snapshot && extensions != null && extensions != isExtension) 1589 return; 1590 1591 if (!onlyInformationIsMapping(all, element)) { 1592 Row row = gen.new Row(); 1593 row.setAnchor(element.getPath()); 1594 row.setColor(getRowColor(element)); 1595 boolean hasDef = element != null; 1596 boolean ext = false; 1597 if (s.equals("extension") || s.equals("modifierExtension")) { 1598 if (element.hasType() && element.getType().get(0).hasProfile() 1599 && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 1600 row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 1601 else 1602 row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1603 ext = true; 1604 } else if (!hasDef || element.getType().size() == 0) 1605 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 1606 else if (hasDef && element.getType().size() > 1) { 1607 if (allTypesAre(element.getType(), "Reference")) 1608 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 1609 else 1610 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 1611 } else if (hasDef && element.getType().get(0).getCode() != null 1612 && element.getType().get(0).getCode().startsWith("@")) 1613 row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE); 1614 else if (hasDef && isPrimitive(element.getType().get(0).getCode())) 1615 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 1616 else if (hasDef && isReference(element.getType().get(0).getCode())) 1617 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 1618 else if (hasDef && isDataType(element.getType().get(0).getCode())) 1619 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 1620 else 1621 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 1622 String ref = defPath == null ? null : defPath + makePathLink(element); 1623 UnusedTracker used = new UnusedTracker(); 1624 used.used = true; 1625 Cell left = gen.new Cell(null, ref, s, !hasDef ? null : element.getDefinition(), null); 1626 row.getCells().add(left); 1627 Cell gc = gen.new Cell(); 1628 row.getCells().add(gc); 1629 if (element != null && element.getIsModifier()) 1630 checkForNoChange(element.getIsModifierElement(), 1631 gc.addStyledText("This element is a modifier element", "?!", null, null, null, false)); 1632 if (element != null && element.getMustSupport()) 1633 checkForNoChange(element.getMustSupportElement(), 1634 gc.addStyledText("This element must be supported", "S", null, null, null, false)); 1635 if (element != null && element.getIsSummary()) 1636 checkForNoChange(element.getIsSummaryElement(), 1637 gc.addStyledText("This element is included in summaries", "?", null, null, null, false)); 1638 if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty())) 1639 gc.addStyledText("This element has or is affected by some invariants", "I", null, null, null, false); 1640 1641 ExtensionContext extDefn = null; 1642 if (ext) { 1643 if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) { 1644 extDefn = locateExtension(StructureDefinition.class, element.getType().get(0).getProfile().get(0).getValue()); 1645 if (extDefn == null) { 1646 genCardinality(gen, element, row, hasDef, used, null); 1647 row.getCells().add(gen.new Cell(null, null, "?? " + element.getType().get(0).getProfile(), null, null)); 1648 generateDescription(gen, row, element, null, used.used, profile.getUrl(), 1649 element.getType().get(0).getProfile().get(0).getValue(), profile, corePath); 1650 } else { 1651 String name = urltail(element.getType().get(0).getProfile().get(0).getValue()); 1652 left.getPieces().get(0).setText(name); 1653 // left.getPieces().get(0).setReference((String) 1654 // extDefn.getExtensionStructure().getTag("filename")); 1655 left.getPieces().get(0).setHint("Extension URL = " + extDefn.getUrl()); 1656 genCardinality(gen, element, row, hasDef, used, extDefn.getElement()); 1657 ElementDefinition valueDefn = extDefn.getExtensionValueDefinition(); 1658 if (valueDefn != null && !"0".equals(valueDefn.getMax())) 1659 genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath); 1660 else // if it's complex, we just call it nothing 1661 // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), 1662 // profileBaseFileName, profile); 1663 row.getCells().add(gen.new Cell(null, null, "(Complex)", null, null)); 1664 generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, 1665 corePath); 1666 } 1667 } else { 1668 genCardinality(gen, element, row, hasDef, used, null); 1669 if ("0".equals(element.getMax())) 1670 row.getCells().add(gen.new Cell()); 1671 else 1672 genTypes(gen, row, element, profileBaseFileName, profile, corePath); 1673 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath); 1674 } 1675 } else { 1676 genCardinality(gen, element, row, hasDef, used, null); 1677 if (hasDef && !"0".equals(element.getMax())) 1678 genTypes(gen, row, element, profileBaseFileName, profile, corePath); 1679 else 1680 row.getCells().add(gen.new Cell()); 1681 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath); 1682 } 1683 if (element.hasSlicing()) { 1684 if (standardExtensionSlicing(element)) { 1685 used.used = element.hasType() && element.getType().get(0).hasProfile(); 1686 showMissing = false; 1687 } else { 1688 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 1689 row.getCells().get(2).getPieces().clear(); 1690 for (Cell cell : row.getCells()) 1691 for (Piece p : cell.getPieces()) { 1692 p.addStyle("font-style: italic"); 1693 } 1694 } 1695 } 1696 if (used.used || showMissing) 1697 rows.add(row); 1698 if (!used.used && !element.hasSlicing()) { 1699 for (Cell cell : row.getCells()) 1700 for (Piece p : cell.getPieces()) { 1701 p.setStyle("text-decoration:line-through"); 1702 p.setReference(null); 1703 } 1704 } else { 1705 for (ElementDefinition child : children) 1706 if (!child.getPath().endsWith(".id")) 1707 genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, 1708 isExtension, snapshot, corePath); 1709 if (!snapshot && (extensions == null || !extensions)) 1710 for (ElementDefinition child : children) 1711 if (child.getPath().endsWith(".extension")) 1712 genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, 1713 false, corePath); 1714 } 1715 } 1716 } 1717 1718 private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value) { 1719 if (value.contains("#")) { 1720 StructureDefinition ext = context.fetchResource(StructureDefinition.class, 1721 value.substring(0, value.indexOf("#"))); 1722 if (ext == null) 1723 return null; 1724 String tail = value.substring(value.indexOf("#") + 1); 1725 ElementDefinition ed = null; 1726 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 1727 if (tail.equals(ted.getName())) { 1728 ed = ted; 1729 return new ExtensionContext(ext, ed); 1730 } 1731 } 1732 return null; 1733 } else { 1734 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 1735 if (ext == null) 1736 return null; 1737 else 1738 return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0)); 1739 } 1740 } 1741 1742 private boolean extensionIsComplex(String value) { 1743 if (value.contains("#")) { 1744 StructureDefinition ext = context.fetchResource(StructureDefinition.class, 1745 value.substring(0, value.indexOf("#"))); 1746 if (ext == null) 1747 return false; 1748 String tail = value.substring(value.indexOf("#") + 1); 1749 ElementDefinition ed = null; 1750 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 1751 if (tail.equals(ted.getName())) { 1752 ed = ted; 1753 break; 1754 } 1755 } 1756 if (ed == null) 1757 return false; 1758 int i = ext.getSnapshot().getElement().indexOf(ed); 1759 int j = i + 1; 1760 while (j < ext.getSnapshot().getElement().size() 1761 && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath())) 1762 j++; 1763 return j - i > 5; 1764 } else { 1765 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 1766 return ext != null && ext.getSnapshot().getElement().size() > 5; 1767 } 1768 } 1769 1770 private String getRowColor(ElementDefinition element) { 1771 switch (element.getUserInt(UD_ERROR_STATUS)) { 1772 case STATUS_OK: 1773 return null; 1774 case STATUS_HINT: 1775 return ROW_COLOR_HINT; 1776 case STATUS_WARNING: 1777 return ROW_COLOR_WARNING; 1778 case STATUS_ERROR: 1779 return ROW_COLOR_ERROR; 1780 case STATUS_FATAL: 1781 return ROW_COLOR_FATAL; 1782 default: 1783 return null; 1784 } 1785 } 1786 1787 private String urltail(String path) { 1788 if (path.contains("#")) 1789 return path.substring(path.lastIndexOf('#') + 1); 1790 if (path.contains("/")) 1791 return path.substring(path.lastIndexOf('/') + 1); 1792 else 1793 return path; 1794 1795 } 1796 1797 private boolean standardExtensionSlicing(ElementDefinition element) { 1798 String t = tail(element.getPath()); 1799 return (t.equals("extension") || t.equals("modifierExtension")) 1800 && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 1801 && element.getSlicing().getDiscriminator().get(0).getValue().equals("url"); 1802 } 1803 1804 private String makePathLink(ElementDefinition element) { 1805 if (!element.hasName()) 1806 return element.getPath(); 1807 if (!element.getPath().contains(".")) 1808 return element.getName(); 1809 return element.getPath().substring(0, element.getPath().lastIndexOf(".")) + "." + element.getName(); 1810 1811 } 1812 1813 private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, 1814 ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, 1815 String corePath) throws IOException { 1816 Cell c = gen.new Cell(); 1817 row.getCells().add(c); 1818 1819 if (used) { 1820 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 1821 c.getPieces().add(checkForNoChange(definition.getFixed(), 1822 gen.new Piece(null, "\"" + buildJson(definition.getFixed()) + "\"", null).addStyle("color: darkgreen"))); 1823 } else { 1824 if (definition != null && definition.hasShort()) { 1825 if (!c.getPieces().isEmpty()) 1826 c.addPiece(gen.new Piece("br")); 1827 c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, definition.getShort(), null))); 1828 } else if (fallback != null && fallback != null && fallback.hasShort()) { 1829 if (!c.getPieces().isEmpty()) 1830 c.addPiece(gen.new Piece("br")); 1831 c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, fallback.getShort(), null))); 1832 } 1833 if (url != null) { 1834 if (!c.getPieces().isEmpty()) 1835 c.addPiece(gen.new Piece("br")); 1836 String fullUrl = url.startsWith("#") ? baseURL + url : url; 1837 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 1838 String ref = ed == null ? null : (String) corePath + ed.getUserData("path"); 1839 c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold")); 1840 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 1841 } 1842 1843 if (definition.hasSlicing()) { 1844 if (!c.getPieces().isEmpty()) 1845 c.addPiece(gen.new Piece("br")); 1846 c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); 1847 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 1848 } 1849 if (definition != null) { 1850 if (definition.hasBinding()) { 1851 if (!c.getPieces().isEmpty()) 1852 c.addPiece(gen.new Piece("br")); 1853 BindingResolution br = pkp.resolveBinding(definition.getBinding()); 1854 c.getPieces().add(checkForNoChange(definition.getBinding(), 1855 gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold"))); 1856 c.getPieces() 1857 .add(checkForNoChange(definition.getBinding(), 1858 gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) ? br.url : corePath + br.url, 1859 br.display, null))); 1860 if (definition.getBinding().hasStrength()) { 1861 c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(null, " (", null))); 1862 c.getPieces() 1863 .add(checkForNoChange(definition.getBinding(), 1864 gen.new Piece(corePath + "terminologies.html#" + definition.getBinding().getStrength().toCode(), 1865 definition.getBinding().getStrength().toCode(), 1866 definition.getBinding().getStrength().getDefinition()))); 1867 c.getPieces().add(gen.new Piece(null, ")", null)); 1868 } 1869 } 1870 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 1871 if (!c.getPieces().isEmpty()) 1872 c.addPiece(gen.new Piece("br")); 1873 c.getPieces().add( 1874 checkForNoChange(inv, gen.new Piece(null, inv.getKey() + ": ", null).addStyle("font-weight:bold"))); 1875 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); 1876 } 1877 if (definition.hasFixed()) { 1878 if (!c.getPieces().isEmpty()) 1879 c.addPiece(gen.new Piece("br")); 1880 c.getPieces().add(checkForNoChange(definition.getFixed(), 1881 gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); 1882 c.getPieces().add(checkForNoChange(definition.getFixed(), 1883 gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen"))); 1884 } else if (definition.hasPattern()) { 1885 if (!c.getPieces().isEmpty()) 1886 c.addPiece(gen.new Piece("br")); 1887 c.getPieces().add(checkForNoChange(definition.getPattern(), 1888 gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); 1889 c.getPieces().add(checkForNoChange(definition.getPattern(), 1890 gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 1891 } else if (definition.hasExample()) { 1892 if (!c.getPieces().isEmpty()) 1893 c.addPiece(gen.new Piece("br")); 1894 c.getPieces().add(checkForNoChange(definition.getExample(), 1895 gen.new Piece(null, "Example: ", null).addStyle("font-weight:bold"))); 1896 c.getPieces().add(checkForNoChange(definition.getExample(), 1897 gen.new Piece(null, buildJson(definition.getExample()), null).addStyle("color: darkgreen"))); 1898 } 1899 } 1900 } 1901 } 1902 return c; 1903 } 1904 1905 private String buildJson(Type value) throws IOException { 1906 if (value instanceof PrimitiveType) 1907 return ((PrimitiveType) value).asStringValue(); 1908 1909 IParser json = context.newJsonParser(); 1910 return json.composeString(value, null); 1911 } 1912 1913 public String describeSlice(ElementDefinitionSlicingComponent slicing) { 1914 return (slicing.getOrdered() ? "Ordered, " : "Unordered, ") + describe(slicing.getRules()) + ", by " 1915 + commas(slicing.getDiscriminator()); 1916 } 1917 1918 private String commas(List<StringType> discriminator) { 1919 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 1920 for (StringType id : discriminator) 1921 c.append(id.asStringValue()); 1922 return c.toString(); 1923 } 1924 1925 private String describe(SlicingRules rules) { 1926 switch (rules) { 1927 case CLOSED: 1928 return "Closed"; 1929 case OPEN: 1930 return "Open"; 1931 case OPENATEND: 1932 return "Open At End"; 1933 default: 1934 return "??"; 1935 } 1936 } 1937 1938 private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) { 1939 return (!e.hasName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) && getChildren(list, e).isEmpty(); 1940 } 1941 1942 private boolean onlyInformationIsMapping(ElementDefinition d) { 1943 return !d.hasShort() && !d.hasDefinition() && !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() 1944 && !d.hasMax() && !d.getType().isEmpty() && !d.hasNameReference() && !d.hasExample() && !d.hasFixed() 1945 && !d.hasMaxLengthElement() && !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() 1946 && !d.hasMustSupportElement() && !d.hasBinding(); 1947 } 1948 1949 private boolean allTypesAre(List<TypeRefComponent> types, String name) { 1950 for (TypeRefComponent t : types) { 1951 if (!t.getCode().equals(name)) 1952 return false; 1953 } 1954 return true; 1955 } 1956 1957 private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) { 1958 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1959 int i = all.indexOf(element) + 1; 1960 while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) { 1961 if ((all.get(i).getPath().substring(0, element.getPath().length() + 1).equals(element.getPath() + ".")) 1962 && !all.get(i).getPath().substring(element.getPath().length() + 1).contains(".")) 1963 result.add(all.get(i)); 1964 i++; 1965 } 1966 return result; 1967 } 1968 1969 private String tail(String path) { 1970 if (path.contains(".")) 1971 return path.substring(path.lastIndexOf('.') + 1); 1972 else 1973 return path; 1974 } 1975 1976 private boolean isDataType(String value) { 1977 return Utilities.existsInList(value, "Identifier", "HumanName", "Address", "ContactPoint", "Timing", 1978 "SimpleQuantity", "Quantity", "Attachment", "Range", "Period", "Ratio", "CodeableConcept", "Coding", 1979 "SampledData", "Age", "Distance", "Duration", "Count", "Money"); 1980 } 1981 1982 private boolean isReference(String value) { 1983 return value.equals("Reference"); 1984 } 1985 1986 public static boolean isPrimitive(String value) { 1987 return value == null || Utilities.existsInListNC(value, "boolean", "integer", "decimal", "base64Binary", "instant", 1988 "string", "date", "dateTime", "code", "oid", "uuid", "id", "uri"); 1989 } 1990 1991// private static String listStructures(StructureDefinition p) { 1992// StringBuilder b = new StringBuilder(); 1993// boolean first = true; 1994// for (ProfileStructureComponent s : p.getStructure()) { 1995// if (first) 1996// first = false; 1997// else 1998// b.append(", "); 1999// if (pkp != null && pkp.hasLinkFor(s.getType())) 2000// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 2001// else 2002// b.append(s.getType()); 2003// } 2004// return b.toString(); 2005// } 2006 2007 public StructureDefinition getProfile(StructureDefinition source, String url) { 2008 StructureDefinition profile; 2009 String code; 2010 if (url.startsWith("#")) { 2011 profile = source; 2012 code = url.substring(1); 2013 } else { 2014 String[] parts = url.split("\\#"); 2015 profile = context.fetchResource(StructureDefinition.class, parts[0]); 2016 code = parts.length == 1 ? null : parts[1]; 2017 } 2018 if (profile == null) 2019 return null; 2020 if (code == null) 2021 return profile; 2022 for (Resource r : profile.getContained()) { 2023 if (r instanceof StructureDefinition && r.getId().equals(code)) 2024 return (StructureDefinition) r; 2025 } 2026 return null; 2027 } 2028 2029 public static class ElementDefinitionHolder { 2030 private String name; 2031 private ElementDefinition self; 2032 private int baseIndex = 0; 2033 private List<ElementDefinitionHolder> children; 2034 2035 public ElementDefinitionHolder(ElementDefinition self) { 2036 super(); 2037 this.self = self; 2038 this.name = self.getPath(); 2039 children = new ArrayList<ElementDefinitionHolder>(); 2040 } 2041 2042 public ElementDefinition getSelf() { 2043 return self; 2044 } 2045 2046 public List<ElementDefinitionHolder> getChildren() { 2047 return children; 2048 } 2049 2050 public int getBaseIndex() { 2051 return baseIndex; 2052 } 2053 2054 public void setBaseIndex(int baseIndex) { 2055 this.baseIndex = baseIndex; 2056 } 2057 2058 } 2059 2060 public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 2061 2062 private boolean inExtension; 2063 private List<ElementDefinition> snapshot; 2064 private int prefixLength; 2065 private String base; 2066 private String name; 2067 private Set<String> errors = new HashSet<String>(); 2068 2069 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, 2070 int prefixLength, String name) { 2071 this.inExtension = inExtension; 2072 this.snapshot = snapshot; 2073 this.prefixLength = prefixLength; 2074 this.base = base; 2075 this.name = name; 2076 } 2077 2078 @Override 2079 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 2080 if (o1.getBaseIndex() == 0) 2081 o1.setBaseIndex(find(o1.getSelf().getPath())); 2082 if (o2.getBaseIndex() == 0) 2083 o2.setBaseIndex(find(o2.getSelf().getPath())); 2084 return o1.getBaseIndex() - o2.getBaseIndex(); 2085 } 2086 2087 private int find(String path) { 2088 String actual = base + path.substring(prefixLength); 2089 for (int i = 0; i < snapshot.size(); i++) { 2090 String p = snapshot.get(i).getPath(); 2091 if (p.equals(actual)) 2092 return i; 2093 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length() - 3)) && !(actual.endsWith("[x]")) 2094 && !actual.substring(p.length() - 3).contains(".")) 2095 return i; 2096 } 2097 if (prefixLength == 0) 2098 errors.add("Differential contains path " + path + " which is not found in the base"); 2099 else 2100 errors.add( 2101 "Differential contains path " + path + " which is actually " + actual + ", which is not found in the base"); 2102 return 0; 2103 } 2104 2105 public void checkForErrors(List<String> errorList) { 2106 if (errors.size() > 0) { 2107// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2108// for (String s : errors) 2109// b.append("StructureDefinition "+name+": "+s); 2110// throw new DefinitionException(b.toString()); 2111 for (String s : errors) 2112 if (s.startsWith("!")) 2113 errorList.add("!StructureDefinition " + name + ": " + s.substring(1)); 2114 else 2115 errorList.add("StructureDefinition " + name + ": " + s); 2116 } 2117 } 2118 } 2119 2120 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) { 2121 2122 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 2123 // first, we move the differential elements into a tree 2124 ElementDefinitionHolder edh = new ElementDefinitionHolder(diffList.get(0)); 2125 2126 boolean hasSlicing = false; 2127 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 2128 for (ElementDefinition elt : diffList) { 2129 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 2130 hasSlicing = true; 2131 break; 2132 } 2133 paths.add(elt.getPath()); 2134 } 2135 if (!hasSlicing) { 2136 // if Differential does not have slicing then safe to pre-sort the list 2137 // so elements and subcomponents are together 2138 Collections.sort(diffList, new ElementNameCompare()); 2139 } 2140 2141 int i = 1; 2142 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 2143 2144 // now, we sort the siblings throughout the tree 2145 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name); 2146 sortElements(edh, cmp, errors); 2147 2148 // now, we serialise them back to a list 2149 diffList.clear(); 2150 writeElements(edh, diffList); 2151 } 2152 2153 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 2154 String path = edh.getSelf().getPath(); 2155 final String prefix = path + "."; 2156 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 2157 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 2158 edh.getChildren().add(child); 2159 i = processElementsIntoTree(child, i + 1, list); 2160 } 2161 return i; 2162 } 2163 2164 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) { 2165 if (edh.getChildren().size() == 1) 2166 // special case - sort needsto allocate base numbers, but there'll be no sort if 2167 // there's only 1 child. So in that case, we just go ahead and allocated base 2168 // number directly 2169 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath()); 2170 else 2171 Collections.sort(edh.getChildren(), cmp); 2172 cmp.checkForErrors(errors); 2173 2174 for (ElementDefinitionHolder child : edh.getChildren()) { 2175 if (child.getChildren().size() > 0) { 2176 // what we have to check for here is running off the base profile into a data 2177 // type profile 2178 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 2179 ElementDefinitionComparer ccmp; 2180 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getCode()) 2181 || ed.getType().get(0).getCode().equals(ed.getPath())) { 2182 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name); 2183 } else if (ed.getType().get(0).getCode().equals("Extension") && child.getSelf().getType().size() == 1 2184 && child.getSelf().getType().get(0).hasProfile()) { 2185 ccmp = new ElementDefinitionComparer(true, 2186 context.fetchResource(StructureDefinition.class, 2187 child.getSelf().getType().get(0).getProfile().get(0).getValue()).getSnapshot().getElement(), 2188 ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name); 2189 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getCode().equals("*")) { 2190 ccmp = new ElementDefinitionComparer(false, 2191 context.fetchTypeDefinition(ed.getType().get(0).getCode()).getSnapshot().getElement(), 2192 ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name); 2193 } else if (child.getSelf().getType().size() == 1) { 2194 ccmp = new ElementDefinitionComparer(false, 2195 context.fetchTypeDefinition(child.getSelf().getType().get(0).getCode()).getSnapshot().getElement(), 2196 child.getSelf().getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name); 2197 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 2198 String p = child.getSelf().getPath().substring(ed.getPath().length() - 3); 2199 StructureDefinition sd = context.fetchTypeDefinition(p); 2200 if (sd == null) 2201 throw new Error("Unable to find profile " + p); 2202 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, 2203 child.getSelf().getPath().length(), cmp.name); 2204 } else { 2205 throw new Error("Not handled yet (sortElements: " + ed.getPath() + ":" + typeCode(ed.getType()) + ")"); 2206 } 2207 sortElements(child, ccmp, errors); 2208 } 2209 } 2210 } 2211 2212 private boolean isAbstract(String code) { 2213 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") 2214 || code.equals("DomainResource"); 2215 } 2216 2217 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 2218 list.add(edh.getSelf()); 2219 for (ElementDefinitionHolder child : edh.getChildren()) { 2220 writeElements(child, list); 2221 } 2222 } 2223 2224 /** 2225 * First compare element by path then by name if same 2226 */ 2227 private static class ElementNameCompare implements Comparator<ElementDefinition> { 2228 2229 @Override 2230 public int compare(ElementDefinition o1, ElementDefinition o2) { 2231 String path1 = normalizePath(o1); 2232 String path2 = normalizePath(o2); 2233 int cmp = path1.compareTo(path2); 2234 if (cmp == 0) { 2235 String name1 = o1.hasName() ? o1.getName() : ""; 2236 String name2 = o2.hasName() ? o2.getName() : ""; 2237 cmp = name1.compareTo(name2); 2238 } 2239 return cmp; 2240 } 2241 2242 private static String normalizePath(ElementDefinition e) { 2243 if (!e.hasPath()) 2244 return ""; 2245 String path = e.getPath(); 2246 // if sorting element names make sure onset[x] appears before onsetAge, 2247 // onsetDate, etc. 2248 // so strip off the [x] suffix when comparing the path names. 2249 if (path.endsWith("[x]")) { 2250 path = path.substring(0, path.length() - 3); 2251 } 2252 return path; 2253 } 2254 2255 } 2256 2257 // generate schematroins for the rules in a structure definition 2258 2259 public void generateSchematrons(OutputStream dest, StructureDefinition structure) 2260 throws IOException, DefinitionException { 2261 if (!structure.hasConstrainedType()) 2262 throw new DefinitionException( 2263 "not the right kind of structure to generate schematrons for (" + structure.getUrl() + ")"); 2264 if (!structure.hasSnapshot()) 2265 throw new DefinitionException("needs a snapshot for (" + structure.getUrl() + ")"); 2266 2267 StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBase()); 2268 2269 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 2270 2271 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 2272 generateForChildren(sch, "f:" + ed.getPath(), ed, structure, base); 2273 sch.dump(); 2274 } 2275 2276 private class Slicer extends ElementDefinitionSlicingComponent { 2277 String criteria = ""; 2278 String name = ""; 2279 boolean check; 2280 2281 public Slicer(boolean cantCheck) { 2282 super(); 2283 this.check = cantCheck; 2284 } 2285 } 2286 2287 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, 2288 StructureDefinition structure) { 2289 // given a child in a structure, it's sliced. figure out the slicing xpath 2290 if (child.getPath().endsWith(".extension")) { 2291 ElementDefinition ued = getUrlFor(structure, child); 2292 if ((ued == null || !ued.hasFixed()) && !(child.getType().get(0).hasProfile())) 2293 return new Slicer(false); 2294 else { 2295 Slicer s = new Slicer(true); 2296 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).asStringValue() 2297 : ((UriType) ued.getFixed()).asStringValue(); 2298 s.name = " with URL = '" + url + "'"; 2299 s.criteria = "[@url = '" + url + "']"; 2300 return s; 2301 } 2302 } else 2303 return new Slicer(false); 2304 } 2305 2306 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, 2307 StructureDefinition structure, StructureDefinition base) throws IOException { 2308 // generateForChild(txt, structure, child); 2309 List<ElementDefinition> children = getChildList(structure, ed); 2310 String sliceName = null; 2311 ElementDefinitionSlicingComponent slicing = null; 2312 for (ElementDefinition child : children) { 2313 String name = tail(child.getPath()); 2314 if (child.hasSlicing()) { 2315 sliceName = name; 2316 slicing = child.getSlicing(); 2317 } else if (!name.equals(sliceName)) 2318 slicing = null; 2319 2320 ElementDefinition based = getByPath(base, child.getPath()); 2321 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 2322 boolean doMax = !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax()))); 2323 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 2324 if (slicer.check) { 2325 if (doMin || doMax) { 2326 Section s = sch.section(xpath); 2327 Rule r = s.rule(xpath); 2328 if (doMin) 2329 r.assrt("count(f:" + name + slicer.criteria + ") >= " + Integer.toString(child.getMin()), 2330 name + slicer.name + ": minimum cardinality of '" + name + "' is " + Integer.toString(child.getMin())); 2331 if (doMax) 2332 r.assrt("count(f:" + name + slicer.criteria + ") <= " + child.getMax(), 2333 name + slicer.name + ": maximum cardinality of '" + name + "' is " + child.getMax()); 2334 } 2335 } 2336 } 2337 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 2338 if (inv.hasXpath()) { 2339 Section s = sch.section(ed.getPath()); 2340 Rule r = s.rule(xpath); 2341 r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId() + ": " : "") + inv.getHuman() 2342 + (inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 2343 } 2344 } 2345 for (ElementDefinition child : children) { 2346 String name = tail(child.getPath()); 2347 generateForChildren(sch, xpath + "/f:" + name, child, structure, base); 2348 } 2349 } 2350 2351 private ElementDefinition getByPath(StructureDefinition base, String path) { 2352 for (ElementDefinition ed : base.getSnapshot().getElement()) { 2353 if (ed.getPath().equals(path)) 2354 return ed; 2355 if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length() - 3 2356 && ed.getPath().substring(0, ed.getPath().length() - 3).equals(path.substring(0, ed.getPath().length() - 3))) 2357 return ed; 2358 } 2359 return null; 2360 } 2361 2362// 2363//private void generateForChild(TextStreamWriter txt, 2364// StructureDefinition structure, ElementDefinition child) { 2365// // TODO Auto-generated method stub 2366// 2367//} 2368 2369}