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