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