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