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