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 if (derived.hasMaxValue()) { 1014 if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false)) 1015 base.setMaxValue(derived.getMaxValue().copy()); 1016 else if (trimDifferential) 1017 derived.setMaxValue(null); 1018 else 1019 derived.getMaxValue().setUserData(DERIVATION_EQUALS, true); 1020 } 1021 1022 if (derived.hasMinValue()) { 1023 if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false)) 1024 base.setMinValue(derived.getMinValue().copy()); 1025 else if (trimDifferential) 1026 derived.setMinValue(null); 1027 else 1028 derived.getMinValue().setUserData(DERIVATION_EQUALS, true); 1029 } 1030 1031 // todo: what to do about conditions? 1032 // condition : id 0..* 1033 1034 if (derived.hasMustSupportElement()) { 1035 if (!Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false)) 1036 base.setMustSupportElement(derived.getMustSupportElement().copy()); 1037 else if (trimDifferential) 1038 derived.setMustSupportElement(null); 1039 else 1040 derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true); 1041 } 1042 1043 1044 // profiles cannot change : isModifier, defaultValue, meaningWhenMissing 1045 // but extensions can change isModifier 1046 if (isExtension) { 1047 if (!Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)) 1048 base.setIsModifierElement(derived.getIsModifierElement().copy()); 1049 else if (trimDifferential) 1050 derived.setIsModifierElement(null); 1051 else 1052 derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true); 1053 } 1054 1055 if (derived.hasBinding()) { 1056 if (!Base.compareDeep(derived.getBinding(), base.getBinding(), false)) { 1057 if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED) 1058 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)); 1059// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode()); 1060 else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED) { 1061 ValueSetExpansionOutcome expBase = context.expandVS(context.fetchResource(ValueSet.class, base.getBinding().getValueSetReference().getReference()), true); 1062 ValueSetExpansionOutcome expDerived = context.expandVS(context.fetchResource(ValueSet.class, derived.getBinding().getValueSetReference().getReference()), true); 1063 if (expBase.getValueset() == null) 1064 messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSetReference().getReference()+" could not be expanded", IssueSeverity.WARNING)); 1065 else if (expDerived.getValueset() == null) 1066 messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" could not be expanded", IssueSeverity.WARNING)); 1067 else if (!isSubset(expBase.getValueset(), expDerived.getValueset())) 1068 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)); 1069 } 1070 base.setBinding(derived.getBinding().copy()); 1071 } else if (trimDifferential) 1072 derived.setBinding(null); 1073 else 1074 derived.getBinding().setUserData(DERIVATION_EQUALS, true); 1075 } // else if (base.hasBinding() && doesn't have bindable type ) 1076 // base 1077 1078 if (derived.hasIsSummaryElement()) { 1079 if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) 1080 base.setIsSummaryElement(derived.getIsSummaryElement().copy()); 1081 else if (trimDifferential) 1082 derived.setIsSummaryElement(null); 1083 else 1084 derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true); 1085 } 1086 1087 if (derived.hasType()) { 1088 if (!Base.compareDeep(derived.getType(), base.getType(), false)) { 1089 if (base.hasType()) { 1090 for (TypeRefComponent ts : derived.getType()) { 1091 boolean ok = false; 1092 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1093 for (TypeRefComponent td : base.getType()) { 1094 b.append(td.getCode()); 1095 if (td.hasCode() && (td.getCode().equals(ts.getCode()) || td.getCode().equals("Extension") || 1096 td.getCode().equals("Element") || td.getCode().equals("*") || 1097 ((td.getCode().equals("Resource") || (td.getCode().equals("DomainResource")) && pkp.isResource(ts.getCode()))))) 1098 ok = true; 1099 } 1100 if (!ok) 1101 throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+ts.getCode()+" from "+b.toString()); 1102 } 1103 } 1104 base.getType().clear(); 1105 for (TypeRefComponent t : derived.getType()) { 1106 TypeRefComponent tt = t.copy(); 1107// tt.setUserData(DERIVATION_EQUALS, true); 1108 base.getType().add(tt); 1109 } 1110 } 1111 else if (trimDifferential) 1112 derived.getType().clear(); 1113 else 1114 for (TypeRefComponent t : derived.getType()) 1115 t.setUserData(DERIVATION_EQUALS, true); 1116 } 1117 1118 if (derived.hasMapping()) { 1119 // todo: mappings are not cumulative - one replaces another 1120 if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) { 1121 for (ElementDefinitionMappingComponent s : derived.getMapping()) { 1122 boolean found = false; 1123 for (ElementDefinitionMappingComponent d : base.getMapping()) { 1124 found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap())); 1125 } 1126 if (!found) 1127 base.getMapping().add(s); 1128 } 1129 } 1130 else if (trimDifferential) 1131 derived.getMapping().clear(); 1132 else 1133 for (ElementDefinitionMappingComponent t : derived.getMapping()) 1134 t.setUserData(DERIVATION_EQUALS, true); 1135 } 1136 1137 // todo: constraints are cumulative. there is no replacing 1138 for (ElementDefinitionConstraintComponent s : base.getConstraint()) 1139 s.setUserData(IS_DERIVED, true); 1140 if (derived.hasConstraint()) { 1141 for (ElementDefinitionConstraintComponent s : derived.getConstraint()) { 1142 base.getConstraint().add(s.copy()); 1143 } 1144 } 1145 } 1146 } 1147 1148 private boolean isLargerMax(String derived, String base) { 1149 if ("*".equals(base)) 1150 return false; 1151 if ("*".equals(derived)) 1152 return true; 1153 return Integer.parseInt(derived) > Integer.parseInt(base); 1154 } 1155 1156 1157 private boolean isSubset(ValueSet expBase, ValueSet expDerived) { 1158 return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion()); 1159 } 1160 1161 1162 private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) { 1163 for (ValueSetExpansionContainsComponent cc : contains) { 1164 if (!inExpansion(cc, expansion.getContains())) 1165 return false; 1166 if (!codesInExpansion(cc.getContains(), expansion)) 1167 return false; 1168 } 1169 return true; 1170 } 1171 1172 1173 private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) { 1174 for (ValueSetExpansionContainsComponent cc1 : contains) { 1175 if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) 1176 return true; 1177 if (inExpansion(cc, cc1.getContains())) 1178 return true; 1179 } 1180 return false; 1181 } 1182 1183 1184 public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, Set<String> outputTracker) throws IOException, FHIRException { 1185 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics); 1186 TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false); 1187 1188 boolean deep = false; 1189 boolean vdeep = false; 1190 for (ElementDefinition eld : ed.getSnapshot().getElement()) { 1191 deep = deep || eld.getPath().contains("Extension.extension."); 1192 vdeep = vdeep || eld.getPath().contains("Extension.extension.extension."); 1193 } 1194 Row r = gen.new Row(); 1195 model.getRows().add(r); 1196 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)); 1197 r.getCells().add(gen.new Cell()); 1198 r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null)); 1199 1200 if (full || vdeep) { 1201 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 1202 1203 r.setIcon(deep ? "icon_extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1204 List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0)); 1205 for (ElementDefinition child : children) 1206 if (!child.getPath().endsWith(".id")) 1207 genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath); 1208 } else if (deep) { 1209 List<ElementDefinition> children = new ArrayList<ElementDefinition>(); 1210 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 1211 if (ted.getPath().equals("Extension.extension")) 1212 children.add(ted); 1213 } 1214 1215 r.getCells().add(gen.new Cell("", "", "Extension", null, null)); 1216 r.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 1217 1218 for (ElementDefinition c : children) { 1219 ElementDefinition ved = getValueFor(ed, c); 1220 ElementDefinition ued = getUrlFor(ed, c); 1221 if (ved != null && ued != null) { 1222 Row r1 = gen.new Row(); 1223 r.getSubRows().add(r1); 1224 r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null)); 1225 r1.getCells().add(gen.new Cell()); 1226 r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null)); 1227 genTypes(gen, r1, ved, defFile, ed, corePath); 1228 r1.getCells().add(gen.new Cell(null, null, c.getDefinition(), null, null)); 1229 r1.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1230 } 1231 } 1232 } else { 1233 ElementDefinition ved = null; 1234 for (ElementDefinition ted : ed.getSnapshot().getElement()) { 1235 if (ted.getPath().startsWith("Extension.value")) 1236 ved = ted; 1237 } 1238 1239 genTypes(gen, r, ved, defFile, ed, corePath); 1240 1241 r.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1242 } 1243 Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null); 1244 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, ed.getName()+": "+ed.getDescription(), null)); 1245 c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null)); 1246 r.getCells().add(c); 1247 1248 1249 return gen.generate(model, corePath, 0, outputTracker); 1250 } 1251 1252 private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { 1253 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 1254 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 1255 if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url")) 1256 return ed.getSnapshot().getElement().get(i); 1257 i++; 1258 } 1259 return null; 1260 } 1261 1262 private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) { 1263 int i = ed.getSnapshot().getElement().indexOf(c) + 1; 1264 while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { 1265 if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value")) 1266 return ed.getSnapshot().getElement().get(i); 1267 i++; 1268 } 1269 return null; 1270 } 1271 1272 1273 private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath) { 1274 Cell c = gen.new Cell(); 1275 r.getCells().add(c); 1276 List<TypeRefComponent> types = e.getType(); 1277 if (!e.hasType()) { 1278 if (e.hasNameReference()) { 1279 ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), e.getNameReference()); 1280 if (ed == null) 1281 c.getPieces().add(gen.new Piece(null, "Unknown reference to "+e.getNameReference(), null)); 1282 else 1283 c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null)); 1284 return c; 1285 } else { 1286 ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER); 1287 if (d != null && d.hasType()) { 1288 types = new ArrayList<ElementDefinition.TypeRefComponent>(); 1289 for (TypeRefComponent tr : d.getType()) { 1290 TypeRefComponent tt = tr.copy(); 1291 tt.setUserData(DERIVATION_EQUALS, true); 1292 types.add(tt); 1293 } 1294 } else 1295 return c; 1296 } 1297 } 1298 1299 boolean first = true; 1300 Element source = types.get(0); // either all types are the same, or we don't consider any of them the same 1301 1302 boolean allReference = ADD_REFERENCE_TO_TABLE && !types.isEmpty(); 1303 for (TypeRefComponent t : types) { 1304 if (!(t.getCode().equals("Reference") && t.hasProfile())) 1305 allReference = false; 1306 } 1307 if (allReference) { 1308 c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null)); 1309 c.getPieces().add(gen.new Piece(null, "(", null)); 1310 } 1311 TypeRefComponent tl = null; 1312 for (TypeRefComponent t : types) { 1313 if (first) 1314 first = false; 1315 else if (allReference) 1316 c.addPiece(checkForNoChange(tl, gen.new Piece(null," | ", null))); 1317 else 1318 c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null))); 1319 tl = t; 1320 if (t.getCode().equals("Reference") || (t.getCode().equals("Resource") && t.hasProfile())) { 1321 if (ADD_REFERENCE_TO_TABLE && !allReference) { 1322 c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null)); 1323 c.getPieces().add(gen.new Piece(null, "(", null)); 1324 } 1325 if (t.hasProfile() && t.getProfile().get(0).getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 1326 StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue()); 1327 if (sd != null) { 1328 String disp = sd.hasDisplay() ? sd.getDisplay() : sd.getName(); 1329 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+sd.getUserString("path"), disp, null))); 1330 } else { 1331 String rn = t.getProfile().get(0).getValue().substring(40); 1332 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+pkp.getLinkFor(rn), rn, null))); 1333 } 1334 } else if (t.getProfile().size() == 0) { 1335 c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null))); 1336 } else if (t.getProfile().get(0).getValue().startsWith("#")) 1337 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+t.getProfile().get(0).getValue().substring(1).toLowerCase()+".html", t.getProfile().get(0).getValue(), null))); 1338 else 1339 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+t.getProfile().get(0).getValue(), t.getProfile().get(0).getValue(), null))); 1340 if (ADD_REFERENCE_TO_TABLE && !allReference) { 1341 c.getPieces().add(gen.new Piece(null, ")", null)); 1342 } 1343 } else if (t.hasProfile()) { // a profiled type 1344 String ref; 1345 ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue()); 1346 if (ref != null) { 1347 String[] parts = ref.split("\\|"); 1348 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+parts[0], parts[1], t.getCode()))); 1349 } else 1350 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+ref, t.getCode(), null))); 1351 } else if (pkp.hasLinkFor(t.getCode())) { 1352 c.addPiece(checkForNoChange(t, gen.new Piece(corePath+pkp.getLinkFor(t.getCode()), t.getCode(), null))); 1353 } else 1354 c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null))); 1355 } 1356 if (allReference) { 1357 c.getPieces().add(gen.new Piece(null, ")", null)); 1358 } 1359 return c; 1360 } 1361 1362 private ElementDefinition getElementByName(List<ElementDefinition> elements, String nameReference) { 1363 for (ElementDefinition ed : elements) 1364 if (ed.hasName() && ed.getName().equals(nameReference)) 1365 return ed; 1366 return null; 1367 } 1368 1369 1370 public static String describeExtensionContext(StructureDefinition ext) { 1371 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1372 for (StringType t : ext.getContext()) 1373 b.append(t.getValue()); 1374 if (!ext.hasContextType()) 1375 throw new Error("no context type on "+ext.getUrl()); 1376 switch (ext.getContextType()) { 1377 case DATATYPE: return "Use on data type: "+b.toString(); 1378 case EXTENSION: return "Use on extension: "+b.toString(); 1379 case RESOURCE: return "Use on element: "+b.toString(); 1380 case MAPPING: return "Use where element has mapping: "+b.toString(); 1381 default: 1382 return "??"; 1383 } 1384 } 1385 1386 private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) { 1387 IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 1388 StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 1389 if (min.isEmpty() && fallback != null) 1390 min = fallback.getMinElement(); 1391 if (max.isEmpty() && fallback != null) 1392 max = fallback.getMaxElement(); 1393 1394 tracker.used = !max.isEmpty() && !max.getValue().equals("0"); 1395 1396 if (min.isEmpty() && max.isEmpty()) 1397 return null; 1398 else 1399 return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue()); 1400 } 1401 1402 private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) { 1403 IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType(); 1404 StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType(); 1405 if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 1406 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 1407 min = base.getMinElement().copy(); 1408 min.setUserData(DERIVATION_EQUALS, true); 1409 } 1410 if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) { 1411 ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER); 1412 max = base.getMaxElement().copy(); 1413 max.setUserData(DERIVATION_EQUALS, true); 1414 } 1415 if (min.isEmpty() && fallback != null) 1416 min = fallback.getMinElement(); 1417 if (max.isEmpty() && fallback != null) 1418 max = fallback.getMaxElement(); 1419 1420 if (!max.isEmpty()) 1421 tracker.used = !max.getValue().equals("0"); 1422 1423 Cell cell = gen.new Cell(null, null, null, null, null); 1424 row.getCells().add(cell); 1425 if (!min.isEmpty() || !max.isEmpty()) { 1426 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null))); 1427 cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null))); 1428 cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null))); 1429 } 1430 } 1431 1432 1433 private Piece checkForNoChange(Element source, Piece piece) { 1434 if (source.hasUserData(DERIVATION_EQUALS)) { 1435 piece.addStyle("opacity: 0.4"); 1436 } 1437 return piece; 1438 } 1439 1440 private Piece checkForNoChange(Element src1, Element src2, Piece piece) { 1441 if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) { 1442 piece.addStyle("opacity: 0.5"); 1443 } 1444 return piece; 1445 } 1446 1447 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 { 1448 assert(diff != snapshot);// check it's ok to get rid of one of these 1449 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics); 1450 TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), false); 1451 List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement(); 1452 List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 1453 profiles.add(profile); 1454 genElement(defFile == null ? null : defFile+"#"+profile.getId()+".", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath); 1455 return gen.generate(model, corePath, 0, outputTracker); 1456 } 1457 1458 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 { 1459 StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); 1460 String s = tail(element.getPath()); 1461 List<ElementDefinition> children = getChildren(all, element); 1462 boolean isExtension = (s.equals("extension") || s.equals("modifierExtension")); 1463 if (!snapshot && extensions != null && extensions != isExtension) 1464 return; 1465 1466 if (!onlyInformationIsMapping(all, element)) { 1467 Row row = gen.new Row(); 1468 row.setAnchor(element.getPath()); 1469 row.setColor(getRowColor(element)); 1470 boolean hasDef = element != null; 1471 boolean ext = false; 1472 if (s.equals("extension") || s.equals("modifierExtension")) { 1473 if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) 1474 row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX); 1475 else 1476 row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE); 1477 ext = true; 1478 } else if (!hasDef || element.getType().size() == 0) 1479 row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); 1480 else if (hasDef && element.getType().size() > 1) { 1481 if (allTypesAre(element.getType(), "Reference")) 1482 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 1483 else 1484 row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); 1485 } else if (hasDef && element.getType().get(0).getCode() != null && element.getType().get(0).getCode().startsWith("@")) 1486 row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE); 1487 else if (hasDef && isPrimitive(element.getType().get(0).getCode())) 1488 row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); 1489 else if (hasDef && isReference(element.getType().get(0).getCode())) 1490 row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); 1491 else if (hasDef && isDataType(element.getType().get(0).getCode())) 1492 row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); 1493 else 1494 row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); 1495 String ref = defPath == null ? null : defPath + makePathLink(element); 1496 UnusedTracker used = new UnusedTracker(); 1497 used.used = true; 1498 Cell left = gen.new Cell(null, ref, s, !hasDef ? null : element.getDefinition(), null); 1499 row.getCells().add(left); 1500 Cell gc = gen.new Cell(); 1501 row.getCells().add(gc); 1502 if (element != null && element.getIsModifier()) 1503 checkForNoChange(element.getIsModifierElement(), gc.addStyledText("This element is a modifier element", "?!", null, null, null, false)); 1504 if (element != null && element.getMustSupport()) 1505 checkForNoChange(element.getMustSupportElement(), gc.addStyledText("This element must be supported", "S", null, null, null, false)); 1506 if (element != null && element.getIsSummary()) 1507 checkForNoChange(element.getIsSummaryElement(), gc.addStyledText("This element is included in summaries", "∑", null, null, null, false)); 1508 if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty())) 1509 gc.addStyledText("This element has or is affected by some invariants", "I", null, null, null, false); 1510 1511 ExtensionContext extDefn = null; 1512 if (ext) { 1513 if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) { 1514 extDefn = locateExtension(StructureDefinition.class, element.getType().get(0).getProfile().get(0).getValue()); 1515 if (extDefn == null) { 1516 genCardinality(gen, element, row, hasDef, used, null); 1517 row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null)); 1518 generateDescription(gen, row, element, null, used.used, profile.getUrl(), element.getType().get(0).getProfile().get(0).getValue(), profile, corePath); 1519 } else { 1520 String name = urltail(element.getType().get(0).getProfile().get(0).getValue()); 1521 left.getPieces().get(0).setText(name); 1522 // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename")); 1523 left.getPieces().get(0).setHint("Extension URL = "+extDefn.getUrl()); 1524 genCardinality(gen, element, row, hasDef, used, extDefn.getElement()); 1525 ElementDefinition valueDefn = extDefn.getExtensionValueDefinition(); 1526 if (valueDefn != null && !"0".equals(valueDefn.getMax())) 1527 genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath); 1528 else // if it's complex, we just call it nothing 1529 // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile); 1530 row.getCells().add(gen.new Cell(null, null, "(Complex)", null, null)); 1531 generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath); 1532 } 1533 } else { 1534 genCardinality(gen, element, row, hasDef, used, null); 1535 if ("0".equals(element.getMax())) 1536 row.getCells().add(gen.new Cell()); 1537 else 1538 genTypes(gen, row, element, profileBaseFileName, profile, corePath); 1539 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath); 1540 } 1541 } else { 1542 genCardinality(gen, element, row, hasDef, used, null); 1543 if (hasDef && !"0".equals(element.getMax())) 1544 genTypes(gen, row, element, profileBaseFileName, profile, corePath); 1545 else 1546 row.getCells().add(gen.new Cell()); 1547 generateDescription(gen, row, element, null, used.used, null, null, profile, corePath); 1548 } 1549 if (element.hasSlicing()) { 1550 if (standardExtensionSlicing(element)) { 1551 used.used = element.hasType() && element.getType().get(0).hasProfile(); 1552 showMissing = false; 1553 } else { 1554 row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE); 1555 row.getCells().get(2).getPieces().clear(); 1556 for (Cell cell : row.getCells()) 1557 for (Piece p : cell.getPieces()) { 1558 p.addStyle("font-style: italic"); 1559 } 1560 } 1561 } 1562 if (used.used || showMissing) 1563 rows.add(row); 1564 if (!used.used && !element.hasSlicing()) { 1565 for (Cell cell : row.getCells()) 1566 for (Piece p : cell.getPieces()) { 1567 p.setStyle("text-decoration:line-through"); 1568 p.setReference(null); 1569 } 1570 } else{ 1571 for (ElementDefinition child : children) 1572 if (!child.getPath().endsWith(".id")) 1573 genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath); 1574 if (!snapshot && (extensions == null || !extensions)) 1575 for (ElementDefinition child : children) 1576 if (child.getPath().endsWith(".extension")) 1577 genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath); 1578 } 1579 } 1580 } 1581 1582 private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value) { 1583 if (value.contains("#")) { 1584 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 1585 if (ext == null) 1586 return null; 1587 String tail = value.substring(value.indexOf("#")+1); 1588 ElementDefinition ed = null; 1589 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 1590 if (tail.equals(ted.getName())) { 1591 ed = ted; 1592 return new ExtensionContext(ext, ed); 1593 } 1594 } 1595 return null; 1596 } else { 1597 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 1598 if (ext == null) 1599 return null; 1600 else 1601 return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0)); 1602 } 1603 } 1604 1605 1606 private boolean extensionIsComplex(String value) { 1607 if (value.contains("#")) { 1608 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#"))); 1609 if (ext == null) 1610 return false; 1611 String tail = value.substring(value.indexOf("#")+1); 1612 ElementDefinition ed = null; 1613 for (ElementDefinition ted : ext.getSnapshot().getElement()) { 1614 if (tail.equals(ted.getName())) { 1615 ed = ted; 1616 break; 1617 } 1618 } 1619 if (ed == null) 1620 return false; 1621 int i = ext.getSnapshot().getElement().indexOf(ed); 1622 int j = i+1; 1623 while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath())) 1624 j++; 1625 return j - i > 5; 1626 } else { 1627 StructureDefinition ext = context.fetchResource(StructureDefinition.class, value); 1628 return ext != null && ext.getSnapshot().getElement().size() > 5; 1629 } 1630 } 1631 1632 1633 private String getRowColor(ElementDefinition element) { 1634 switch (element.getUserInt(UD_ERROR_STATUS)) { 1635 case STATUS_OK: return null; 1636 case STATUS_HINT: return ROW_COLOR_HINT; 1637 case STATUS_WARNING: return ROW_COLOR_WARNING; 1638 case STATUS_ERROR: return ROW_COLOR_ERROR; 1639 case STATUS_FATAL: return ROW_COLOR_FATAL; 1640 default: return null; 1641 } 1642 } 1643 1644 1645 private String urltail(String path) { 1646 if (path.contains("#")) 1647 return path.substring(path.lastIndexOf('#')+1); 1648 if (path.contains("/")) 1649 return path.substring(path.lastIndexOf('/')+1); 1650 else 1651 return path; 1652 1653 } 1654 1655 private boolean standardExtensionSlicing(ElementDefinition element) { 1656 String t = tail(element.getPath()); 1657 return (t.equals("extension") || t.equals("modifierExtension")) 1658 && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getValue().equals("url"); 1659 } 1660 1661 1662 private String makePathLink(ElementDefinition element) { 1663 if (!element.hasName()) 1664 return element.getPath(); 1665 if (!element.getPath().contains(".")) 1666 return element.getName(); 1667 return element.getPath().substring(0, element.getPath().lastIndexOf("."))+"."+element.getName(); 1668 1669 } 1670 1671 private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath) throws IOException { 1672 Cell c = gen.new Cell(); 1673 row.getCells().add(c); 1674 1675 if (used) { 1676 if (definition.getPath().endsWith("url") && definition.hasFixed()) { 1677 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); 1678 } else { 1679 if (definition != null && definition.hasShort()) { 1680 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 1681 c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, definition.getShort(), null))); 1682 } else if (fallback != null && fallback != null && fallback.hasShort()) { 1683 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 1684 c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, fallback.getShort(), null))); 1685 } 1686 if (url != null) { 1687 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 1688 String fullUrl = url.startsWith("#") ? baseURL+url : url; 1689 StructureDefinition ed = context.fetchResource(StructureDefinition.class, url); 1690 String ref = ed == null ? null : (String) corePath+ed.getUserData("path"); 1691 c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold")); 1692 c.getPieces().add(gen.new Piece(ref, fullUrl, null)); 1693 } 1694 1695 if (definition.hasSlicing()) { 1696 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 1697 c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); 1698 c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); 1699 } 1700 if (definition != null) { 1701 if (definition.hasBinding()) { 1702 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 1703 BindingResolution br = pkp.resolveBinding(definition.getBinding()); 1704 c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold"))); 1705 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))); 1706 if (definition.getBinding().hasStrength()) { 1707 c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(null, " (", null))); 1708 c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(corePath+"terminologies.html#"+definition.getBinding().getStrength().toCode(), definition.getBinding().getStrength().toCode(), definition.getBinding().getStrength().getDefinition()))); 1709 c.getPieces().add(gen.new Piece(null, ")", null)); 1710 } 1711 } 1712 for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { 1713 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 1714 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); 1715 c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); 1716 } 1717 if (definition.hasFixed()) { 1718 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 1719 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); 1720 c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen"))); 1721 } else if (definition.hasPattern()) { 1722 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 1723 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); 1724 c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); 1725 } else if (definition.hasExample()) { 1726 if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); 1727 c.getPieces().add(checkForNoChange(definition.getExample(), gen.new Piece(null, "Example: ", null).addStyle("font-weight:bold"))); 1728 c.getPieces().add(checkForNoChange(definition.getExample(), gen.new Piece(null, buildJson(definition.getExample()), null).addStyle("color: darkgreen"))); 1729 } 1730 } 1731 } 1732 } 1733 return c; 1734 } 1735 1736 private String buildJson(Type value) throws IOException { 1737 if (value instanceof PrimitiveType) 1738 return ((PrimitiveType) value).asStringValue(); 1739 1740 IParser json = context.newJsonParser(); 1741 return json.composeString(value, null); 1742 } 1743 1744 1745 public String describeSlice(ElementDefinitionSlicingComponent slicing) { 1746 return (slicing.getOrdered() ? "Ordered, " : "Unordered, ")+describe(slicing.getRules())+", by "+commas(slicing.getDiscriminator()); 1747 } 1748 1749 private String commas(List<StringType> discriminator) { 1750 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 1751 for (StringType id : discriminator) 1752 c.append(id.asStringValue()); 1753 return c.toString(); 1754 } 1755 1756 1757 private String describe(SlicingRules rules) { 1758 switch (rules) { 1759 case CLOSED : return "Closed"; 1760 case OPEN : return "Open"; 1761 case OPENATEND : return "Open At End"; 1762 default: 1763 return "??"; 1764 } 1765 } 1766 1767 private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) { 1768 return (!e.hasName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) && 1769 getChildren(list, e).isEmpty(); 1770 } 1771 1772 private boolean onlyInformationIsMapping(ElementDefinition d) { 1773 return !d.hasShort() && !d.hasDefinition() && 1774 !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() && 1775 !d.hasMax() && !d.getType().isEmpty() && !d.hasNameReference() && 1776 !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() && 1777 !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() && 1778 !d.hasBinding(); 1779 } 1780 1781 private boolean allTypesAre(List<TypeRefComponent> types, String name) { 1782 for (TypeRefComponent t : types) { 1783 if (!t.getCode().equals(name)) 1784 return false; 1785 } 1786 return true; 1787 } 1788 1789 private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) { 1790 List<ElementDefinition> result = new ArrayList<ElementDefinition>(); 1791 int i = all.indexOf(element)+1; 1792 while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) { 1793 if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains(".")) 1794 result.add(all.get(i)); 1795 i++; 1796 } 1797 return result; 1798 } 1799 1800 private String tail(String path) { 1801 if (path.contains(".")) 1802 return path.substring(path.lastIndexOf('.')+1); 1803 else 1804 return path; 1805 } 1806 1807 private boolean isDataType(String value) { 1808 return Utilities.existsInList(value, "Identifier", "HumanName", "Address", "ContactPoint", "Timing", "SimpleQuantity", "Quantity", "Attachment", "Range", 1809 "Period", "Ratio", "CodeableConcept", "Coding", "SampledData", "Age", "Distance", "Duration", "Count", "Money"); 1810 } 1811 1812 private boolean isReference(String value) { 1813 return value.equals("Reference"); 1814 } 1815 1816 public static boolean isPrimitive(String value) { 1817 return value == null || Utilities.existsInListNC(value, "boolean", "integer", "decimal", "base64Binary", "instant", "string", "date", "dateTime", "code", "oid", "uuid", "id", "uri"); 1818 } 1819 1820// private static String listStructures(StructureDefinition p) { 1821// StringBuilder b = new StringBuilder(); 1822// boolean first = true; 1823// for (ProfileStructureComponent s : p.getStructure()) { 1824// if (first) 1825// first = false; 1826// else 1827// b.append(", "); 1828// if (pkp != null && pkp.hasLinkFor(s.getType())) 1829// b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>"); 1830// else 1831// b.append(s.getType()); 1832// } 1833// return b.toString(); 1834// } 1835 1836 1837 public StructureDefinition getProfile(StructureDefinition source, String url) { 1838 StructureDefinition profile; 1839 String code; 1840 if (url.startsWith("#")) { 1841 profile = source; 1842 code = url.substring(1); 1843 } else { 1844 String[] parts = url.split("\\#"); 1845 profile = context.fetchResource(StructureDefinition.class, parts[0]); 1846 code = parts.length == 1 ? null : parts[1]; 1847 } 1848 if (profile == null) 1849 return null; 1850 if (code == null) 1851 return profile; 1852 for (Resource r : profile.getContained()) { 1853 if (r instanceof StructureDefinition && r.getId().equals(code)) 1854 return (StructureDefinition) r; 1855 } 1856 return null; 1857 } 1858 1859 1860 1861 public static class ElementDefinitionHolder { 1862 private String name; 1863 private ElementDefinition self; 1864 private int baseIndex = 0; 1865 private List<ElementDefinitionHolder> children; 1866 1867 public ElementDefinitionHolder(ElementDefinition self) { 1868 super(); 1869 this.self = self; 1870 this.name = self.getPath(); 1871 children = new ArrayList<ElementDefinitionHolder>(); 1872 } 1873 1874 public ElementDefinition getSelf() { 1875 return self; 1876 } 1877 1878 public List<ElementDefinitionHolder> getChildren() { 1879 return children; 1880 } 1881 1882 public int getBaseIndex() { 1883 return baseIndex; 1884 } 1885 1886 public void setBaseIndex(int baseIndex) { 1887 this.baseIndex = baseIndex; 1888 } 1889 1890 } 1891 1892 public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> { 1893 1894 private boolean inExtension; 1895 private List<ElementDefinition> snapshot; 1896 private int prefixLength; 1897 private String base; 1898 private String name; 1899 private Set<String> errors = new HashSet<String>(); 1900 1901 public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) { 1902 this.inExtension = inExtension; 1903 this.snapshot = snapshot; 1904 this.prefixLength = prefixLength; 1905 this.base = base; 1906 this.name = name; 1907 } 1908 1909 @Override 1910 public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { 1911 if (o1.getBaseIndex() == 0) 1912 o1.setBaseIndex(find(o1.getSelf().getPath())); 1913 if (o2.getBaseIndex() == 0) 1914 o2.setBaseIndex(find(o2.getSelf().getPath())); 1915 return o1.getBaseIndex() - o2.getBaseIndex(); 1916 } 1917 1918 private int find(String path) { 1919 String actual = base+path.substring(prefixLength); 1920 for (int i = 0; i < snapshot.size(); i++) { 1921 String p = snapshot.get(i).getPath(); 1922 if (p.equals(actual)) 1923 return i; 1924 if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) 1925 return i; 1926 } 1927 if (prefixLength == 0) 1928 errors.add("Differential contains path "+path+" which is not found in the base"); 1929 else 1930 errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base"); 1931 return 0; 1932 } 1933 1934 public void checkForErrors(List<String> errorList) { 1935 if (errors.size() > 0) { 1936// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1937// for (String s : errors) 1938// b.append("StructureDefinition "+name+": "+s); 1939// throw new DefinitionException(b.toString()); 1940 for (String s : errors) 1941 if (s.startsWith("!")) 1942 errorList.add("!StructureDefinition "+name+": "+s.substring(1)); 1943 else 1944 errorList.add("StructureDefinition "+name+": "+s); 1945 } 1946 } 1947 } 1948 1949 1950 public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) { 1951 1952 final List<ElementDefinition> diffList = diff.getDifferential().getElement(); 1953 // first, we move the differential elements into a tree 1954 ElementDefinitionHolder edh = new ElementDefinitionHolder(diffList.get(0)); 1955 1956 boolean hasSlicing = false; 1957 List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly 1958 for(ElementDefinition elt : diffList) { 1959 if (elt.hasSlicing() || paths.contains(elt.getPath())) { 1960 hasSlicing = true; 1961 break; 1962 } 1963 paths.add(elt.getPath()); 1964 } 1965 if(!hasSlicing) { 1966 // if Differential does not have slicing then safe to pre-sort the list 1967 // so elements and subcomponents are together 1968 Collections.sort(diffList, new ElementNameCompare()); 1969 } 1970 1971 int i = 1; 1972 processElementsIntoTree(edh, i, diff.getDifferential().getElement()); 1973 1974 // now, we sort the siblings throughout the tree 1975 ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name); 1976 sortElements(edh, cmp, errors); 1977 1978 // now, we serialise them back to a list 1979 diffList.clear(); 1980 writeElements(edh, diffList); 1981 } 1982 1983 private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) { 1984 String path = edh.getSelf().getPath(); 1985 final String prefix = path + "."; 1986 while (i < list.size() && list.get(i).getPath().startsWith(prefix)) { 1987 ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i)); 1988 edh.getChildren().add(child); 1989 i = processElementsIntoTree(child, i+1, list); 1990 } 1991 return i; 1992 } 1993 1994 private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) { 1995 if (edh.getChildren().size() == 1) 1996 // 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 1997 edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath()); 1998 else 1999 Collections.sort(edh.getChildren(), cmp); 2000 cmp.checkForErrors(errors); 2001 2002 for (ElementDefinitionHolder child : edh.getChildren()) { 2003 if (child.getChildren().size() > 0) { 2004 // what we have to check for here is running off the base profile into a data type profile 2005 ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); 2006 ElementDefinitionComparer ccmp; 2007 if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getCode()) || ed.getType().get(0).getCode().equals(ed.getPath())) { 2008 ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name); 2009 } else if (ed.getType().get(0).getCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) { 2010 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); 2011 } else if (ed.getType().size() == 1 && !ed.getType().get(0).getCode().equals("*")) { 2012 ccmp = new ElementDefinitionComparer(false, context.fetchTypeDefinition(ed.getType().get(0).getCode()).getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name); 2013 } else if (child.getSelf().getType().size() == 1) { 2014 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); 2015 } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) { 2016 String p = child.getSelf().getPath().substring(ed.getPath().length()-3); 2017 StructureDefinition sd = context.fetchTypeDefinition(p); 2018 if (sd == null) 2019 throw new Error("Unable to find profile "+p); 2020 ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name); 2021 } else { 2022 throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")"); 2023 } 2024 sortElements(child, ccmp, errors); 2025 } 2026 } 2027 } 2028 2029 private boolean isAbstract(String code) { 2030 return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource"); 2031 } 2032 2033 2034 private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) { 2035 list.add(edh.getSelf()); 2036 for (ElementDefinitionHolder child : edh.getChildren()) { 2037 writeElements(child, list); 2038 } 2039 } 2040 2041 /** 2042 * First compare element by path then by name if same 2043 */ 2044 private static class ElementNameCompare implements Comparator<ElementDefinition> { 2045 2046 @Override 2047 public int compare(ElementDefinition o1, ElementDefinition o2) { 2048 String path1 = normalizePath(o1); 2049 String path2 = normalizePath(o2); 2050 int cmp = path1.compareTo(path2); 2051 if (cmp == 0) { 2052 String name1 = o1.hasName() ? o1.getName() : ""; 2053 String name2 = o2.hasName() ? o2.getName() : ""; 2054 cmp = name1.compareTo(name2); 2055 } 2056 return cmp; 2057 } 2058 2059 private static String normalizePath(ElementDefinition e) { 2060 if (!e.hasPath()) return ""; 2061 String path = e.getPath(); 2062 // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc. 2063 // so strip off the [x] suffix when comparing the path names. 2064 if (path.endsWith("[x]")) { 2065 path = path.substring(0, path.length()-3); 2066 } 2067 return path; 2068 } 2069 2070 } 2071 2072 // generate schematroins for the rules in a structure definition 2073 2074 public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException { 2075 if (!structure.hasConstrainedType()) 2076 throw new DefinitionException("not the right kind of structure to generate schematrons for ("+structure.getUrl()+")"); 2077 if (!structure.hasSnapshot()) 2078 throw new DefinitionException("needs a snapshot for ("+structure.getUrl()+")"); 2079 2080 StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBase()); 2081 2082 SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName()); 2083 2084 ElementDefinition ed = structure.getSnapshot().getElement().get(0); 2085 generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base); 2086 sch.dump(); 2087 } 2088 2089 private class Slicer extends ElementDefinitionSlicingComponent { 2090 String criteria = ""; 2091 String name = ""; 2092 boolean check; 2093 public Slicer(boolean cantCheck) { 2094 super(); 2095 this.check = cantCheck; 2096 } 2097 } 2098 2099 private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) { 2100 // given a child in a structure, it's sliced. figure out the slicing xpath 2101 if (child.getPath().endsWith(".extension")) { 2102 ElementDefinition ued = getUrlFor(structure, child); 2103 if ((ued == null || !ued.hasFixed()) && !(child.getType().get(0).hasProfile())) 2104 return new Slicer(false); 2105 else { 2106 Slicer s = new Slicer(true); 2107 String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).asStringValue() : ((UriType) ued.getFixed()).asStringValue(); 2108 s.name = " with URL = '"+url+"'"; 2109 s.criteria = "[@url = '"+url+"']"; 2110 return s; 2111 } 2112 } else 2113 return new Slicer(false); 2114 } 2115 2116 private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException { 2117 // generateForChild(txt, structure, child); 2118 List<ElementDefinition> children = getChildList(structure, ed); 2119 String sliceName = null; 2120 ElementDefinitionSlicingComponent slicing = null; 2121 for (ElementDefinition child : children) { 2122 String name = tail(child.getPath()); 2123 if (child.hasSlicing()) { 2124 sliceName = name; 2125 slicing = child.getSlicing(); 2126 } else if (!name.equals(sliceName)) 2127 slicing = null; 2128 2129 ElementDefinition based = getByPath(base, child.getPath()); 2130 boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin())); 2131 boolean doMax = !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax()))); 2132 Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure); 2133 if (slicer.check) { 2134 if (doMin || doMax) { 2135 Section s = sch.section(xpath); 2136 Rule r = s.rule(xpath); 2137 if (doMin) 2138 r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin())); 2139 if (doMax) 2140 r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax()); 2141 } 2142 } 2143 } 2144 for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { 2145 if (inv.hasXpath()) { 2146 Section s = sch.section(ed.getPath()); 2147 Rule r = s.rule(xpath); 2148 r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : "")); 2149 } 2150 } 2151 for (ElementDefinition child : children) { 2152 String name = tail(child.getPath()); 2153 generateForChildren(sch, xpath+"/f:"+name, child, structure, base); 2154 } 2155 } 2156 2157 2158 2159 2160 private ElementDefinition getByPath(StructureDefinition base, String path) { 2161 for (ElementDefinition ed : base.getSnapshot().getElement()) { 2162 if (ed.getPath().equals(path)) 2163 return ed; 2164 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))) 2165 return ed; 2166 } 2167 return null; 2168 } 2169 2170// 2171//private void generateForChild(TextStreamWriter txt, 2172// StructureDefinition structure, ElementDefinition child) { 2173// // TODO Auto-generated method stub 2174// 2175//} 2176 2177 2178}