001package org.hl7.fhir.r4.conformance; 002 003import java.util.ArrayList; 004import java.util.Arrays; 005import java.util.Collections; 006import java.util.Comparator; 007import java.util.HashSet; 008import java.util.LinkedList; 009import java.util.List; 010 011import org.apache.commons.lang3.StringUtils; 012import org.apache.commons.lang3.tuple.ImmutablePair; 013import org.apache.commons.lang3.tuple.Pair; 014import org.hl7.fhir.r4.context.IWorkerContext; 015import org.hl7.fhir.r4.elementmodel.TurtleParser; 016import org.hl7.fhir.r4.model.DomainResource; 017import org.hl7.fhir.r4.model.ElementDefinition; 018import org.hl7.fhir.r4.model.Enumerations; 019import org.hl7.fhir.r4.model.Reference; 020import org.hl7.fhir.r4.model.Resource; 021import org.hl7.fhir.r4.model.StructureDefinition; 022import org.hl7.fhir.r4.model.Type; 023import org.hl7.fhir.r4.model.UriType; 024import org.hl7.fhir.r4.model.ValueSet; 025import org.hl7.fhir.r4.terminologies.ValueSetExpander; 026import org.hl7.fhir.r4.utils.ToolingExtensions; 027import org.hl7.fhir.exceptions.FHIRException; 028import org.stringtemplate.v4.ST; 029 030public class ShExGenerator { 031 032 public enum HTMLLinkPolicy { 033 NONE, EXTERNAL, INTERNAL 034 } 035 public boolean doDatatypes = true; // add data types 036 public boolean withComments = true; // include comments 037 public boolean completeModel = false; // doing complete build (fhir.shex) 038 039 040 private static String SHEX_TEMPLATE = "$header$\n\n" + 041 "$shapeDefinitions$"; 042 043 // A header is a list of prefixes, a base declaration and a start node 044 private static String FHIR = "http://hl7.org/fhir/"; 045 private static String FHIR_VS = FHIR + "ValueSet/"; 046 private static String HEADER_TEMPLATE = 047 "PREFIX fhir: <$fhir$> \n" + 048 "PREFIX fhirvs: <$fhirvs$>\n" + 049 "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> \n" + 050 "BASE <http://hl7.org/fhir/shape/>\n$start$"; 051 052 // Start template for single (open) entry 053 private static String START_TEMPLATE = "\n\nstart=@<$id$> AND {fhir:nodeRole [fhir:treeRoot]}\n"; 054 055 // Start template for complete (closed) model 056 private static String ALL_START_TEMPLATE = "\n\nstart=@<All>\n"; 057 058 private static String ALL_TEMPLATE = "\n<All> $all_entries$\n"; 059 060 private static String ALL_ENTRY_TEMPLATE = "(NOT { fhir:nodeRole [fhir:treeRoot] ; a [fhir:$id$] } OR @<$id$>)"; 061 062 063 // Shape Definition 064 // the shape name 065 // an optional resource declaration (type + treeRoot) 066 // the list of element declarations 067 // an optional index element (for appearances inside ordered lists) 068 private static String SHAPE_DEFINITION_TEMPLATE = 069 "$comment$\n<$id$> CLOSED {\n $resourceDecl$" + 070 "\n $elements$" + 071 "\n fhir:index xsd:integer? # Relative position in a list\n}\n"; 072 073 // Resource Definition 074 // an open shape of type Resource. Used when completeModel = false. 075 private static String RESOURCE_SHAPE_TEMPLATE = 076 "$comment$\n<Resource> {a .+;" + 077 "\n $elements$" + 078 "\n fhir:index xsd:integer?" + 079 "\n}\n"; 080 081 // If we have knowledge of all of the possible resources available to us (completeModel = true), we can build 082 // a model of all possible resources. 083 private static String COMPLETE_RESOURCE_TEMPLATE = 084 "<Resource> @<$resources$>" + 085 "\n\n"; 086 087 // Resource Declaration 088 // a type node 089 // an optional treeRoot declaration (identifies the entry point) 090 private static String RESOURCE_DECL_TEMPLATE = "\na [fhir:$id$];$root$"; 091 092 // Root Declaration. 093 private static String ROOT_TEMPLATE = "\nfhir:nodeRole [fhir:treeRoot]?;"; 094 095 // Element 096 // a predicate, type and cardinality triple 097 private static String ELEMENT_TEMPLATE = "$id$$defn$$card$$comment$"; 098 private static int COMMENT_COL = 40; 099 private static int MAX_CHARS = 35; 100 private static int MIN_COMMENT_SEP = 2; 101 102 // Inner Shape Definition 103 private static String INNER_SHAPE_TEMPLATE = "($comment$\n $defn$\n)$card$"; 104 105 // Simple Element 106 // a shape reference 107 private static String SIMPLE_ELEMENT_DEFN_TEMPLATE = "@<$typ$>$vsdef$"; 108 109 // Value Set Element 110 private static String VALUESET_DEFN_TEMPLATE = " AND\n\t{fhir:value @$vsn$}"; 111 112 // Fixed Value Template 113 private static String FIXED_VALUE_TEMPLATE = " AND\n\t{fhir:value [\"$val$\"]}"; 114 115 // A primitive element definition 116 // the actual type reference 117 private static String PRIMITIVE_ELEMENT_DEFN_TEMPLATE = "$typ$$facets$"; 118 119 // Facets 120 private static String MINVALUE_TEMPLATE = " MININCLUSIVE $val$"; 121 private static String MAXVALUE_TEMPLATE = " MAXINCLUSIVE $val$"; 122 private static String MAXLENGTH_TEMPLATE = " MAXLENGTH $val$"; 123 private static String PATTERN_TEMPLATE = " PATTERN \"$val$\""; 124 125 // A choice of alternative shape definitions 126 // rendered as an inner anonymous shape 127 private static String ALTERNATIVE_SHAPES_TEMPLATE = "fhir:$id$$comment$\n( $altEntries$\n)$card$"; 128 129 // A typed reference definition 130 private static String REFERENCE_DEFN_TEMPLATE = "@<$ref$Reference>"; 131 132 // What we emit for an xhtml 133 private static String XHTML_TYPE_TEMPLATE = "xsd:string"; 134 135 // Additional type for Coding 136 private static String CONCEPT_REFERENCE_TEMPLATE = "a NONLITERAL?;"; 137 138 // Additional type for CodedConcept 139 private static String CONCEPT_REFERENCES_TEMPLATE = "a NONLITERAL*;"; 140 141 // Untyped resource has the extra link entry 142 private static String RESOURCE_LINK_TEMPLATE = "fhir:link IRI?;"; 143 144 // Extension template 145 // No longer used -- we emit the actual definition 146// private static String EXTENSION_TEMPLATE = "<Extension> {fhir:extension @<Extension>*;" + 147// "\n fhir:index xsd:integer?" + 148// "\n}\n"; 149 150 // A typed reference -- a fhir:uri with an optional type and the possibility of a resolvable shape 151 private static String TYPED_REFERENCE_TEMPLATE = "\n<$refType$Reference> CLOSED {" + 152 "\n fhir:Element.id @<id>?;" + 153 "\n fhir:Element.extension @<Extension>*;" + 154 "\n fhir:link @<$refType$> OR CLOSED {a [fhir:$refType$]}?;" + 155 "\n fhir:Reference.reference @<string>?;" + 156 "\n fhir:Reference.display @<string>?;" + 157 "\n fhir:index xsd:integer?" + 158 "\n}"; 159 160 private static String TARGET_REFERENCE_TEMPLATE = "\n<$refType$> {" + 161 "\n a [fhir:$refType$];" + 162 "\n fhir:nodeRole [fhir:treeRoot]?" + 163 "\n}"; 164 165 // A value set definition 166 private static String VALUE_SET_DEFINITION = "# $comment$\n$vsuri$$val_list$\n"; 167 168 169 /** 170 * this makes internal metadata services available to the generator - retrieving structure definitions, and value set expansion etc 171 */ 172 private IWorkerContext context; 173 174 /** 175 * innerTypes -- inner complex types. Currently flattened in ShEx (doesn't have to be, btw) 176 * emittedInnerTypes -- set of inner types that have been generated 177 * datatypes, emittedDatatypes -- types used in the definition, types that have been generated 178 * references -- Reference types (Patient, Specimen, etc) 179 * uniq_structures -- set of structures on the to be generated list... 180 * doDataTypes -- whether or not to emit the data types. 181 */ 182 private HashSet<Pair<StructureDefinition, ElementDefinition>> innerTypes, emittedInnerTypes; 183 private HashSet<String> datatypes, emittedDatatypes; 184 private HashSet<String> references; 185 private LinkedList<StructureDefinition> uniq_structures; 186 private HashSet<String> uniq_structure_urls; 187 private HashSet<ValueSet> required_value_sets; 188 private HashSet<String> known_resources; // Used when generating a full definition 189 190 public ShExGenerator(IWorkerContext context) { 191 super(); 192 this.context = context; 193 innerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>(); 194 emittedInnerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>(); 195 datatypes = new HashSet<String>(); 196 emittedDatatypes = new HashSet<String>(); 197 references = new HashSet<String>(); 198 required_value_sets = new HashSet<ValueSet>(); 199 known_resources = new HashSet<String>(); 200 } 201 202 public String generate(HTMLLinkPolicy links, StructureDefinition structure) { 203 List<StructureDefinition> list = new ArrayList<StructureDefinition>(); 204 list.add(structure); 205 innerTypes.clear(); 206 emittedInnerTypes.clear(); 207 datatypes.clear(); 208 emittedDatatypes.clear(); 209 references.clear(); 210 required_value_sets.clear(); 211 known_resources.clear(); 212 return generate(links, list); 213 } 214 215 public class SortById implements Comparator<StructureDefinition> { 216 217 @Override 218 public int compare(StructureDefinition arg0, StructureDefinition arg1) { 219 return arg0.getId().compareTo(arg1.getId()); 220 } 221 222 } 223 224 private ST tmplt(String template) { 225 return new ST(template, '$', '$'); 226 } 227 228 /** 229 * this is called externally to generate a set of structures to a single ShEx file 230 * generally, it will be called with a single structure, or a long list of structures (all of them) 231 * 232 * @param links HTML link rendering policy 233 * @param structures list of structure definitions to render 234 * @return ShEx definition of structures 235 */ 236 public String generate(HTMLLinkPolicy links, List<StructureDefinition> structures) { 237 238 ST shex_def = tmplt(SHEX_TEMPLATE); 239 String start_cmd; 240 if(completeModel || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE)) 241// || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE)) 242 start_cmd = completeModel? tmplt(ALL_START_TEMPLATE).render() : 243 tmplt(START_TEMPLATE).add("id", structures.get(0).getId()).render(); 244 else 245 start_cmd = ""; 246 shex_def.add("header", tmplt(HEADER_TEMPLATE). 247 add("start", start_cmd). 248 add("fhir", FHIR). 249 add("fhirvs", FHIR_VS).render()); 250 251 Collections.sort(structures, new SortById()); 252 StringBuilder shapeDefinitions = new StringBuilder(); 253 254 // For unknown reasons, the list of structures carries duplicates. We remove them 255 // Also, it is possible for the same sd to have multiple hashes... 256 uniq_structures = new LinkedList<StructureDefinition>(); 257 uniq_structure_urls = new HashSet<String>(); 258 for (StructureDefinition sd : structures) { 259 if (!uniq_structure_urls.contains(sd.getUrl())) { 260 uniq_structures.add(sd); 261 uniq_structure_urls.add(sd.getUrl()); 262 } 263 } 264 265 266 for (StructureDefinition sd : uniq_structures) { 267 shapeDefinitions.append(genShapeDefinition(sd, true)); 268 } 269 270 shapeDefinitions.append(emitInnerTypes()); 271 if(doDatatypes) { 272 shapeDefinitions.append("\n#---------------------- Data Types -------------------\n"); 273 while (emittedDatatypes.size() < datatypes.size() || 274 emittedInnerTypes.size() < innerTypes.size()) { 275 shapeDefinitions.append(emitDataTypes()); 276 shapeDefinitions.append(emitInnerTypes()); 277 } 278 } 279 280 shapeDefinitions.append("\n#---------------------- Reference Types -------------------\n"); 281 for(String r: references) { 282 shapeDefinitions.append("\n").append(tmplt(TYPED_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n"); 283 if (!"Resource".equals(r) && !known_resources.contains(r)) 284 shapeDefinitions.append("\n").append(tmplt(TARGET_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n"); 285 } 286 shex_def.add("shapeDefinitions", shapeDefinitions); 287 288 if(completeModel && known_resources.size() > 0) { 289 shapeDefinitions.append("\n").append(tmplt(COMPLETE_RESOURCE_TEMPLATE) 290 .add("resources", StringUtils.join(known_resources, "> OR\n\t@<")).render()); 291 List<String> all_entries = new ArrayList<String>(); 292 for(String kr: known_resources) 293 all_entries.add(tmplt(ALL_ENTRY_TEMPLATE).add("id", kr).render()); 294 shapeDefinitions.append("\n").append(tmplt(ALL_TEMPLATE) 295 .add("all_entries", StringUtils.join(all_entries, " OR\n\t")).render()); 296 } 297 298 shapeDefinitions.append("\n#---------------------- Value Sets ------------------------\n"); 299 for(ValueSet vs: required_value_sets) 300 shapeDefinitions.append("\n").append(genValueSet(vs)); 301 return shex_def.render(); 302 } 303 304 305 /** 306 * Emit a ShEx definition for the supplied StructureDefinition 307 * @param sd Structure definition to emit 308 * @param top_level True means outermost type, False means recursively called 309 * @return ShEx definition 310 */ 311 private String genShapeDefinition(StructureDefinition sd, boolean top_level) { 312 // xhtml is treated as an atom 313 if("xhtml".equals(sd.getName()) || (completeModel && "Resource".equals(sd.getName()))) 314 return ""; 315 316 ST shape_defn; 317 // Resources are either incomplete items or consist of everything that is defined as a resource (completeModel) 318 if("Resource".equals(sd.getName())) { 319 shape_defn = tmplt(RESOURCE_SHAPE_TEMPLATE); 320 known_resources.add(sd.getName()); 321 } else { 322 shape_defn = tmplt(SHAPE_DEFINITION_TEMPLATE).add("id", sd.getId()); 323 if (sd.getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE)) { 324// || sd.getKind().equals(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE)) { 325 known_resources.add(sd.getName()); 326 ST resource_decl = tmplt(RESOURCE_DECL_TEMPLATE). 327 add("id", sd.getId()). 328 add("root", tmplt(ROOT_TEMPLATE)); 329// add("root", top_level ? tmplt(ROOT_TEMPLATE) : ""); 330 shape_defn.add("resourceDecl", resource_decl.render()); 331 } else { 332 shape_defn.add("resourceDecl", ""); 333 } 334 } 335 336 // Generate the defining elements 337 List<String> elements = new ArrayList<String>(); 338 339 // Add the additional entries for special types 340 String sdn = sd.getName(); 341 if (sdn.equals("Coding")) 342 elements.add(tmplt(CONCEPT_REFERENCE_TEMPLATE).render()); 343 else if (sdn.equals("CodeableConcept")) 344 elements.add(tmplt(CONCEPT_REFERENCES_TEMPLATE).render()); 345 else if (sdn.equals("Reference")) 346 elements.add(tmplt(RESOURCE_LINK_TEMPLATE).render()); 347// else if (sdn.equals("Extension")) 348// return tmplt(EXTENSION_TEMPLATE).render(); 349 350 String root_comment = null; 351 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 352 if(!ed.getPath().contains(".")) 353 root_comment = ed.getShort(); 354 else if (StringUtils.countMatches(ed.getPath(), ".") == 1 && !"0".equals(ed.getMax())) { 355 elements.add(genElementDefinition(sd, ed)); 356 } 357 } 358 shape_defn.add("elements", StringUtils.join(elements, "\n")); 359 shape_defn.add("comment", root_comment == null? " " : "# " + root_comment); 360 return shape_defn.render(); 361 } 362 363 364 /** 365 * Generate a flattened definition for the inner types 366 * @return stringified inner type definitions 367 */ 368 private String emitInnerTypes() { 369 StringBuilder itDefs = new StringBuilder(); 370 while(emittedInnerTypes.size() < innerTypes.size()) { 371 for (Pair<StructureDefinition, ElementDefinition> it : new HashSet<Pair<StructureDefinition, ElementDefinition>>(innerTypes)) { 372 if (!emittedInnerTypes.contains(it)) { 373 itDefs.append("\n").append(genInnerTypeDef(it.getLeft(), it.getRight())); 374 emittedInnerTypes.add(it); 375 } 376 } 377 } 378 return itDefs.toString(); 379 } 380 381 /** 382 * Generate a shape definition for the current set of datatypes 383 * @return stringified data type definitions 384 */ 385 private String emitDataTypes() { 386 StringBuilder dtDefs = new StringBuilder(); 387 while (emittedDatatypes.size() < datatypes.size()) { 388 for (String dt : new HashSet<String>(datatypes)) { 389 if (!emittedDatatypes.contains(dt)) { 390 StructureDefinition sd = context.fetchResource(StructureDefinition.class, 391 ProfileUtilities.sdNs(dt)); 392 // TODO: Figure out why the line below doesn't work 393 // if (sd != null && !uniq_structures.contains(sd)) 394 if(sd != null && !uniq_structure_urls.contains(sd.getUrl())) 395 dtDefs.append("\n").append(genShapeDefinition(sd, false)); 396 emittedDatatypes.add(dt); 397 } 398 } 399 } 400 return dtDefs.toString(); 401 } 402 403 private ArrayList<String> split_text(String text, int max_col) { 404 int pos = 0; 405 ArrayList<String> rval = new ArrayList<String>(); 406 if (text.length() <= max_col) { 407 rval.add(text); 408 } else { 409 String[] words = text.split(" "); 410 int word_idx = 0; 411 while(word_idx < words.length) { 412 StringBuilder accum = new StringBuilder(); 413 while (word_idx < words.length && accum.length() + words[word_idx].length() < max_col) 414 accum.append(words[word_idx++] + " "); 415 if (accum.length() == 0) { 416 accum.append(words[word_idx].substring(0, max_col - 3) + "-"); 417 words[word_idx] = words[word_idx].substring(max_col - 3); 418 } 419 rval.add(accum.toString()); 420 accum = new StringBuilder(); 421 } 422 } 423 return rval; 424 } 425 426 private void addComment(ST tmplt, ElementDefinition ed) { 427 if(withComments && ed.hasShort() && !ed.getId().startsWith("Extension.")) { 428 int nspaces; 429 char[] sep; 430 nspaces = Integer.max(COMMENT_COL - tmplt.add("comment", "#").render().indexOf('#'), MIN_COMMENT_SEP); 431 tmplt.remove("comment"); 432 sep = new char[nspaces]; 433 Arrays.fill(sep, ' '); 434 ArrayList<String> comment_lines = split_text(ed.getShort().replace("\n", " "), MAX_CHARS); 435 StringBuilder comment = new StringBuilder("# "); 436 char[] indent = new char[COMMENT_COL]; 437 Arrays.fill(indent, ' '); 438 for(int i = 0; i < comment_lines.size();) { 439 comment.append(comment_lines.get(i++)); 440 if(i < comment_lines.size()) 441 comment.append("\n" + new String(indent) + "# "); 442 } 443 tmplt.add("comment", new String(sep) + comment.toString()); 444 } else { 445 tmplt.add("comment", " "); 446 } 447 } 448 449 450 /** 451 * Generate a ShEx element definition 452 * @param sd Containing structure definition 453 * @param ed Containing element definition 454 * @return ShEx definition 455 */ 456 private String genElementDefinition(StructureDefinition sd, ElementDefinition ed) { 457 String id = ed.hasBase() ? ed.getBase().getPath() : ed.getPath(); 458 String shortId = id.substring(id.lastIndexOf(".") + 1); 459 String defn; 460 ST element_def; 461 String card = ("*".equals(ed.getMax()) ? (ed.getMin() == 0 ? "*" : "+") : (ed.getMin() == 0 ? "?" : "")) + ";"; 462 463 if(id.endsWith("[x]")) { 464 element_def = ed.getType().size() > 1? tmplt(INNER_SHAPE_TEMPLATE) : tmplt(ELEMENT_TEMPLATE); 465 element_def.add("id", ""); 466 } else { 467 element_def = tmplt(ELEMENT_TEMPLATE); 468 element_def.add("id", "fhir:" + (id.charAt(0) == id.toLowerCase().charAt(0)? shortId : id) + " "); 469 } 470 471 List<ElementDefinition> children = ProfileUtilities.getChildList(sd, ed); 472 if (children.size() > 0) { 473 innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed)); 474 defn = simpleElement(sd, ed, id); 475 } else if(id.endsWith("[x]")) { 476 defn = genChoiceTypes(sd, ed, id); 477 } 478 else if (ed.getType().size() == 1) { 479 // Single entry 480 defn = genTypeRef(sd, ed, id, ed.getType().get(0)); 481 } else if (ed.getContentReference() != null) { 482 // Reference to another element 483 String ref = ed.getContentReference(); 484 if(!ref.startsWith("#")) 485 throw new AssertionError("Not equipped to deal with absolute path references: " + ref); 486 String refPath = null; 487 for(ElementDefinition ed1: sd.getSnapshot().getElement()) { 488 if(ed1.getId() != null && ed1.getId().equals(ref.substring(1))) { 489 refPath = ed1.getPath(); 490 break; 491 } 492 } 493 if(refPath == null) 494 throw new AssertionError("Reference path not found: " + ref); 495 // String typ = id.substring(0, id.indexOf(".") + 1) + ed.getContentReference().substring(1); 496 defn = simpleElement(sd, ed, refPath); 497 } else if(id.endsWith("[x]")) { 498 defn = genChoiceTypes(sd, ed, id); 499 } else { 500 // TODO: Refactoring required here 501 element_def = genAlternativeTypes(ed, id, shortId); 502 element_def.add("id", id.charAt(0) == id.toLowerCase().charAt(0)? shortId : id); 503 element_def.add("card", card); 504 addComment(element_def, ed); 505 return element_def.render(); 506 } 507 element_def.add("defn", defn); 508 element_def.add("card", card); 509 addComment(element_def, ed); 510 return element_def.render(); 511 } 512 513 /** 514 * Generate a type reference and optional value set definition 515 * @param sd Containing StructureDefinition 516 * @param ed Element being defined 517 * @param typ Element type 518 * @return Type definition 519 */ 520 private String simpleElement(StructureDefinition sd, ElementDefinition ed, String typ) { 521 String addldef = ""; 522 ElementDefinition.ElementDefinitionBindingComponent binding = ed.getBinding(); 523 if(binding.hasStrength() && binding.getStrength() == Enumerations.BindingStrength.REQUIRED && "code".equals(typ)) { 524 ValueSet vs = resolveBindingReference(sd, binding.getValueSet()); 525 if (vs != null) { 526 addldef = tmplt(VALUESET_DEFN_TEMPLATE).add("vsn", vsprefix(vs.getUrl())).render(); 527 required_value_sets.add(vs); 528 } 529 } 530 // TODO: check whether value sets and fixed are mutually exclusive 531 if(ed.hasFixed()) { 532 addldef = tmplt(FIXED_VALUE_TEMPLATE).add("val", ed.getFixed().primitiveValue()).render(); 533 } 534 return tmplt(SIMPLE_ELEMENT_DEFN_TEMPLATE).add("typ", typ).add("vsdef", addldef).render(); 535 } 536 537 private String vsprefix(String uri) { 538 if(uri.startsWith(FHIR_VS)) 539 return "fhirvs:" + uri.replace(FHIR_VS, ""); 540 return "<" + uri + ">"; 541 } 542 543 /** 544 * Generate a type reference 545 * @param sd Containing structure definition 546 * @param ed Containing element definition 547 * @param id Element id 548 * @param typ Element type 549 * @return Type reference string 550 */ 551 private String genTypeRef(StructureDefinition sd, ElementDefinition ed, String id, ElementDefinition.TypeRefComponent typ) { 552 553 if(typ.hasProfile()) { 554 if(typ.getCode().equals("Reference")) 555 return genReference("", typ); 556 else if(ProfileUtilities.getChildList(sd, ed).size() > 0) { 557 // inline anonymous type - give it a name and factor it out 558 innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed)); 559 return simpleElement(sd, ed, id); 560 } 561 else { 562 String ref = getTypeName(typ); 563 datatypes.add(ref); 564 return simpleElement(sd, ed, ref); 565 } 566 567 } else if (typ.getCodeElement().getExtensionsByUrl(ToolingExtensions.EXT_RDF_TYPE).size() > 0) { 568 String xt = null; 569 try { 570 xt = typ.getCodeElement().getExtensionString(ToolingExtensions.EXT_RDF_TYPE); 571 } catch (FHIRException e) { 572 e.printStackTrace(); 573 } 574 // TODO: Remove the next line when the type of token gets switched to string 575 // TODO: Add a rdf-type entry for valueInteger to xsd:integer (instead of int) 576 ST td_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE).add("typ", 577 xt.replace("xsd:token", "xsd:string").replace("xsd:int", "xsd:integer")); 578 StringBuilder facets = new StringBuilder(); 579 if(ed.hasMinValue()) { 580 Type mv = ed.getMinValue(); 581 facets.append(tmplt(MINVALUE_TEMPLATE).add("val", TurtleParser.ttlLiteral(mv.primitiveValue(), mv.fhirType())).render()); 582 } 583 if(ed.hasMaxValue()) { 584 Type mv = ed.getMaxValue(); 585 facets.append(tmplt(MAXVALUE_TEMPLATE).add("val", TurtleParser.ttlLiteral(mv.primitiveValue(), mv.fhirType())).render()); 586 } 587 if(ed.hasMaxLength()) { 588 int ml = ed.getMaxLength(); 589 facets.append(tmplt(MAXLENGTH_TEMPLATE).add("val", ml).render()); 590 } 591 if(ed.hasPattern()) { 592 Type pat = ed.getPattern(); 593 facets.append(tmplt(PATTERN_TEMPLATE).add("val",pat.primitiveValue()).render()); 594 } 595 td_entry.add("facets", facets.toString()); 596 return td_entry.render(); 597 598 } else if (typ.getCode() == null) { 599 ST primitive_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE); 600 primitive_entry.add("typ", "xsd:string"); 601 return primitive_entry.render(); 602 603 } else if(typ.getCode().equals("xhtml")) { 604 return tmplt(XHTML_TYPE_TEMPLATE).render(); 605 } else { 606 datatypes.add(typ.getCode()); 607 return simpleElement(sd, ed, typ.getCode()); 608 } 609 } 610 611 /** 612 * Generate a set of alternative shapes 613 * @param ed Containing element definition 614 * @param id Element definition identifier 615 * @param shortId id to use in the actual definition 616 * @return ShEx list of alternative anonymous shapes separated by "OR" 617 */ 618 private ST genAlternativeTypes(ElementDefinition ed, String id, String shortId) { 619 ST shex_alt = tmplt(ALTERNATIVE_SHAPES_TEMPLATE); 620 List<String> altEntries = new ArrayList<String>(); 621 622 623 for(ElementDefinition.TypeRefComponent typ : ed.getType()) { 624 altEntries.add(genAltEntry(id, typ)); 625 } 626 shex_alt.add("altEntries", StringUtils.join(altEntries, " OR\n ")); 627 return shex_alt; 628 } 629 630 631 632 /** 633 * Generate an alternative shape for a reference 634 * @param id reference name 635 * @param typ shape type 636 * @return ShEx equivalent 637 */ 638 private String genAltEntry(String id, ElementDefinition.TypeRefComponent typ) { 639 if(!typ.getCode().equals("Reference")) 640 throw new AssertionError("We do not handle " + typ.getCode() + " alternatives"); 641 642 return genReference(id, typ); 643 } 644 645 /** 646 * Generate a list of type choices for a "name[x]" style id 647 * @param sd Structure containing ed 648 * @param ed element definition 649 * @param id choice identifier 650 * @return ShEx fragment for the set of choices 651 */ 652 private String genChoiceTypes(StructureDefinition sd, ElementDefinition ed, String id) { 653 List<String> choiceEntries = new ArrayList<String>(); 654 String base = id.replace("[x]", ""); 655 656 for(ElementDefinition.TypeRefComponent typ : ed.getType()) 657 choiceEntries.add(genChoiceEntry(sd, ed, id, base, typ)); 658 659 return StringUtils.join(choiceEntries, " |\n"); 660 } 661 662 /** 663 * Generate an entry in a choice list 664 * @param base base identifier 665 * @param typ type/discriminant 666 * @return ShEx fragment for choice entry 667 */ 668 private String genChoiceEntry(StructureDefinition sd, ElementDefinition ed, String id, String base, ElementDefinition.TypeRefComponent typ) { 669 ST shex_choice_entry = tmplt(ELEMENT_TEMPLATE); 670 671 String ext = typ.getCode(); 672 shex_choice_entry.add("id", "fhir:" + base+Character.toUpperCase(ext.charAt(0)) + ext.substring(1) + " "); 673 shex_choice_entry.add("card", ""); 674 shex_choice_entry.add("defn", genTypeRef(sd, ed, id, typ)); 675 shex_choice_entry.add("comment", " "); 676 return shex_choice_entry.render(); 677 } 678 679 /** 680 * Generate a definition for a referenced element 681 * @param sd Containing structure definition 682 * @param ed Inner element 683 * @return ShEx representation of element reference 684 */ 685 private String genInnerTypeDef(StructureDefinition sd, ElementDefinition ed) { 686 String path = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();; 687 ST element_reference = tmplt(SHAPE_DEFINITION_TEMPLATE); 688 element_reference.add("resourceDecl", ""); // Not a resource 689 element_reference.add("id", path); 690 String comment = ed.getShort(); 691 element_reference.add("comment", comment == null? " " : "# " + comment); 692 693 List<String> elements = new ArrayList<String>(); 694 for (ElementDefinition child: ProfileUtilities.getChildList(sd, path, null)) 695 elements.add(genElementDefinition(sd, child)); 696 697 element_reference.add("elements", StringUtils.join(elements, "\n")); 698 return element_reference.render(); 699 } 700 701 /** 702 * Generate a reference to a resource 703 * @param id attribute identifier 704 * @param typ possible reference types 705 * @return string that represents the result 706 */ 707 private String genReference(String id, ElementDefinition.TypeRefComponent typ) { 708 ST shex_ref = tmplt(REFERENCE_DEFN_TEMPLATE); 709 710 String ref = getTypeName(typ); 711 shex_ref.add("id", id); 712 shex_ref.add("ref", ref); 713 references.add(ref); 714 return shex_ref.render(); 715 } 716 717 /** 718 * Return the type name for typ 719 * @param typ type to get name for 720 * @return name 721 */ 722 private String getTypeName(ElementDefinition.TypeRefComponent typ) { 723 // TODO: This is brittle. There has to be a utility to do this... 724 if (typ.hasTargetProfile()) { 725 String[] els = typ.getTargetProfile().get(0).getValue().split("/"); 726 return els[els.length - 1]; 727 } else if (typ.hasProfile()) { 728 String[] els = typ.getProfile().get(0).getValue().split("/"); 729 return els[els.length - 1]; 730 } else { 731 return typ.getCode(); 732 } 733 } 734 735 private String genValueSet(ValueSet vs) { 736 ST vsd = tmplt(VALUE_SET_DEFINITION).add("vsuri", vsprefix(vs.getUrl())).add("comment", vs.getDescription()); 737 ValueSetExpander.ValueSetExpansionOutcome vse = context.expandVS(vs, true, false); 738 List<String> valid_codes = new ArrayList<String>(); 739 if(vse != null && 740 vse.getValueset() != null && 741 vse.getValueset().hasExpansion() && 742 vse.getValueset().getExpansion().hasContains()) { 743 for(ValueSet.ValueSetExpansionContainsComponent vsec : vse.getValueset().getExpansion().getContains()) 744 valid_codes.add("\"" + vsec.getCode() + "\""); 745 } 746 return vsd.add("val_list", valid_codes.size() > 0? " [" + StringUtils.join(valid_codes, " ") + ']' : " EXTERNAL").render(); 747 } 748 749 750 // TODO: find a utility that implements this 751 private ValueSet resolveBindingReference(DomainResource ctxt, Type reference) { 752 if (reference instanceof UriType) { 753 return context.fetchResource(ValueSet.class, ((UriType) reference).getValue().toString()); 754 } 755 else if (reference instanceof Reference) { 756 String s = ((Reference) reference).getReference(); 757 if (s.startsWith("#")) { 758 for (Resource c : ctxt.getContained()) { 759 if (c.getId().equals(s.substring(1)) && (c instanceof ValueSet)) 760 return (ValueSet) c; 761 } 762 return null; 763 } else { 764 return context.fetchResource(ValueSet.class, ((Reference) reference).getReference()); 765 } 766 } 767 else 768 return null; 769 } 770}