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