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