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