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