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