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