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