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